Browse Source

`sourceMapBaseUrl` wip implementation

sanex3339 8 years ago
parent
commit
2dd6f91b7f

+ 7 - 0
README.md

@@ -114,6 +114,7 @@ Following options available for the JS Obfuscator:
     rotateUnicodeArray: true,
     selfDefending: true,
     sourceMap: false,
+    sourceMapBaseUrl: '',
     sourceMapMode: 'separate',
     unicodeArray: true,
     unicodeArrayThreshold: 0.8,
@@ -137,6 +138,7 @@ Following options available for the JS Obfuscator:
     --rotateUnicodeArray <boolean>
     --selfDefending <boolean>
     --sourceMap <boolean>
+    --sourceMapBaseUrl <string>
     --sourceMapMode <string> [inline, separate]
     --unicodeArray <boolean>
     --unicodeArrayThreshold <number>
@@ -220,6 +222,11 @@ Type: `boolean` Default: `false`
 
 Enables source map generation for obfuscated code.
 
+### `sourceMapBaseUrl`
+Type: `string` Default: ``
+
+Inserts custom Url for source map
+
 ### `sourceMapMode`
 Type: `string` Default: `separate`
 

+ 78 - 6
dist/index.js

@@ -145,6 +145,18 @@ var Utils = function () {
             var radix = 16;
             return Number(dec).toString(radix);
         }
+    }, {
+        key: 'extractDomainFromUrl',
+        value: function extractDomainFromUrl(url) {
+            var domain = void 0;
+            if (url.indexOf('://') > -1 || url.indexOf('//') === 0) {
+                domain = url.split('/')[2];
+            } else {
+                domain = url.split('/')[0];
+            }
+            domain = domain.split(':')[0];
+            return domain;
+        }
     }, {
         key: 'getRandomGenerator',
         value: function getRandomGenerator() {
@@ -183,9 +195,9 @@ var Utils = function () {
                 return result;
             };
             var customPool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
-            var randomString = Utils.randomGenerator.string({ length: length, pool: customPool });
-            var randomStringDiff = randomString.replace(new RegExp('[' + escapeRegExp(str) + ']', 'g'), '');
-            var randomStringDiffArray = randomStringDiff.split('');
+            var randomString = Utils.randomGenerator.string({ length: length, pool: customPool }),
+                randomStringDiff = randomString.replace(new RegExp('[' + escapeRegExp(str) + ']', 'g'), ''),
+                randomStringDiffArray = randomStringDiff.split('');
             Utils.randomGenerator.shuffle(randomStringDiffArray);
             randomStringDiff = randomStringDiffArray.join('');
             return [randomMerge(str, randomStringDiff), randomStringDiff];
@@ -883,6 +895,7 @@ exports.NO_CUSTOM_NODES_PRESET = Object.freeze({
     rotateUnicodeArray: false,
     selfDefending: false,
     sourceMap: false,
+    sourceMapBaseUrl: '',
     sourceMapMode: SourceMapMode_1.SourceMapMode.Separate,
     unicodeArray: false,
     unicodeArrayThreshold: 0,
@@ -921,6 +934,9 @@ var JavaScriptObfuscatorInternal = function () {
     _createClass(JavaScriptObfuscatorInternal, [{
         key: 'getObfuscationResult',
         value: function getObfuscationResult() {
+            if (this.options.sourceMapBaseUrl) {
+                this.setSourceMapUrl(this.options.sourceMapBaseUrl);
+            }
             return new SourceMapCorrector_1.SourceMapCorrector(new ObfuscationResult_1.ObfuscationResult(this.generatorOutput.code, this.generatorOutput.map), this.sourceMapUrl, this.options.sourceMapMode).correct();
         }
     }, {
@@ -1132,6 +1148,7 @@ exports.DEFAULT_PRESET = Object.freeze({
     rotateUnicodeArray: true,
     selfDefending: true,
     sourceMap: false,
+    sourceMapBaseUrl: '',
     sourceMapMode: SourceMapMode_1.SourceMapMode.Separate,
     unicodeArray: true,
     unicodeArrayThreshold: 0.8,
@@ -1528,7 +1545,7 @@ var JavaScriptObfuscatorCLI = function () {
         value: function configureCommands() {
             this.commands = new commander.Command().version(JavaScriptObfuscatorCLI.getBuildVersion(), '-v, --version').usage('<inputPath> [options]').option('-o, --output <path>', 'Output path for obfuscated code').option('--compact <boolean>', 'Disable one line output code compacting', JavaScriptObfuscatorCLI.parseBoolean).option('--debugProtection <boolean>', 'Disable browser Debug panel (can cause DevTools enabled browser freeze)', JavaScriptObfuscatorCLI.parseBoolean).option('--debugProtectionInterval <boolean>', 'Disable browser Debug panel even after page was loaded (can cause DevTools enabled browser freeze)', JavaScriptObfuscatorCLI.parseBoolean).option('--disableConsoleOutput <boolean>', 'Allow console.log, console.info, console.error and console.warn messages output into browser console', JavaScriptObfuscatorCLI.parseBoolean).option('--encodeUnicodeLiterals <boolean>', 'All literals in Unicode array become encoded in Base64 (this option can slightly slow down your code speed)', JavaScriptObfuscatorCLI.parseBoolean).option('--reservedNames <list>', 'Disable obfuscation of variable names, function names and names of function parameters that match the passed RegExp patterns (comma separated)', function (val) {
                 return val.split(',');
-            }).option('--rotateUnicodeArray <boolean>', 'Disable rotation of unicode array values during obfuscation', JavaScriptObfuscatorCLI.parseBoolean).option('--selfDefending <boolean>', 'Disables self-defending for obfuscated code', JavaScriptObfuscatorCLI.parseBoolean).option('--sourceMap <boolean>', 'Enables source map generation', JavaScriptObfuscatorCLI.parseBoolean).option('--sourceMapMode <string> [inline, separate]', 'Specify source map output mode', JavaScriptObfuscatorCLI.parseSourceMapMode).option('--unicodeArray <boolean>', 'Disables gathering of all literal strings into an array and replacing every literal string with an array call', JavaScriptObfuscatorCLI.parseBoolean).option('--unicodeArrayThreshold <number>', 'The probability that the literal string will be inserted into unicodeArray (Default: 0.8, Min: 0, Max: 1)', parseFloat).option('--wrapUnicodeArrayCalls <boolean>', 'Disables usage of special access function instead of direct array call', JavaScriptObfuscatorCLI.parseBoolean).option('--domainLock <list>', 'Blocks the execution of the code in domains that do not match the passed RegExp patterns (comma separated)', function (val) {
+            }).option('--rotateUnicodeArray <boolean>', 'Disable rotation of unicode array values during obfuscation', JavaScriptObfuscatorCLI.parseBoolean).option('--selfDefending <boolean>', 'Disables self-defending for obfuscated code', JavaScriptObfuscatorCLI.parseBoolean).option('--sourceMap <boolean>', 'Enables source map generation', JavaScriptObfuscatorCLI.parseBoolean).option('--sourceMapBaseUrl <boolean>', 'Inserts custom Url for source map').option('--sourceMapMode <string> [inline, separate]', 'Specify source map output mode', JavaScriptObfuscatorCLI.parseSourceMapMode).option('--unicodeArray <boolean>', 'Disables gathering of all literal strings into an array and replacing every literal string with an array call', JavaScriptObfuscatorCLI.parseBoolean).option('--unicodeArrayThreshold <number>', 'The probability that the literal string will be inserted into unicodeArray (Default: 0.8, Min: 0, Max: 1)', parseFloat).option('--wrapUnicodeArrayCalls <boolean>', 'Disables usage of special access function instead of direct array call', JavaScriptObfuscatorCLI.parseBoolean).option('--domainLock <list>', 'Blocks the execution of the code in domains that do not match the passed RegExp patterns (comma separated)', function (val) {
                 return val.split(',');
             }).parse(this.rawArguments);
             this.commands.on('--help', function () {
@@ -3213,6 +3230,12 @@ __decorate([class_validator_1.IsString({
 __decorate([class_validator_1.IsBoolean(), __metadata('design:type', Boolean)], Options.prototype, "rotateUnicodeArray", void 0);
 __decorate([class_validator_1.IsBoolean(), __metadata('design:type', Boolean)], Options.prototype, "selfDefending", void 0);
 __decorate([class_validator_1.IsBoolean(), __metadata('design:type', Boolean)], Options.prototype, "sourceMap", void 0);
+__decorate([class_validator_1.IsString(), class_validator_1.ValidateIf(function (options) {
+    return Boolean(options.sourceMapBaseUrl);
+}), class_validator_1.IsUrl({
+    require_protocol: false,
+    require_valid_protocol: true
+}), __metadata('design:type', String)], Options.prototype, "sourceMapBaseUrl", void 0);
 __decorate([class_validator_1.IsIn(['inline', 'separate']), __metadata('design:type', String)], Options.prototype, "sourceMapMode", void 0);
 __decorate([class_validator_1.IsBoolean(), __metadata('design:type', Boolean)], Options.prototype, "unicodeArray", void 0);
 __decorate([class_validator_1.IsNumber(), class_validator_1.Min(0), class_validator_1.Max(1), __metadata('design:type', Number)], Options.prototype, "unicodeArrayThreshold", void 0);
@@ -3221,7 +3244,7 @@ exports.Options = Options;
 
 /***/ },
 /* 55 */
-/***/ function(module, exports) {
+/***/ function(module, exports, __webpack_require__) {
 
 "use strict";
 "use strict";
@@ -3230,6 +3253,8 @@ var _createClass = function () { function defineProperties(target, props) { for
 
 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
 
+var Utils_1 = __webpack_require__(0);
+
 var OptionsNormalizer = function () {
     function OptionsNormalizer() {
         _classCallCheck(this, OptionsNormalizer);
@@ -3266,6 +3291,42 @@ var OptionsNormalizer = function () {
 
             return normalizedOptions;
         }
+    }, {
+        key: "domainLockRule",
+        value: function domainLockRule(options) {
+            if (options.domainLock.length) {
+                var normalizedDomains = [];
+                var _iteratorNormalCompletion2 = true;
+                var _didIteratorError2 = false;
+                var _iteratorError2 = undefined;
+
+                try {
+                    for (var _iterator2 = options.domainLock[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
+                        var domain = _step2.value;
+
+                        normalizedDomains.push(Utils_1.Utils.extractDomainFromUrl(domain));
+                    }
+                } catch (err) {
+                    _didIteratorError2 = true;
+                    _iteratorError2 = err;
+                } finally {
+                    try {
+                        if (!_iteratorNormalCompletion2 && _iterator2.return) {
+                            _iterator2.return();
+                        }
+                    } finally {
+                        if (_didIteratorError2) {
+                            throw _iteratorError2;
+                        }
+                    }
+                }
+
+                Object.assign(options, {
+                    domainLock: normalizedDomains
+                });
+            }
+            return options;
+        }
     }, {
         key: "encodeUnicodeLiteralsRule",
         value: function encodeUnicodeLiteralsRule(options) {
@@ -3282,6 +3343,14 @@ var OptionsNormalizer = function () {
             }
             return options;
         }
+    }, {
+        key: "sourceMapBaseUrl",
+        value: function sourceMapBaseUrl(options) {
+            if (options.sourceMapBaseUrl) {
+                Object.assign(options, OptionsNormalizer.SOURCE_MAP_BASE_URL_OPTIONS);
+            }
+            return options;
+        }
     }, {
         key: "unicodeArrayRule",
         value: function unicodeArrayRule(options) {
@@ -3318,7 +3387,10 @@ OptionsNormalizer.SELF_DEFENDING_OPTIONS = {
     compact: true,
     selfDefending: true
 };
-OptionsNormalizer.normalizerRules = [OptionsNormalizer.unicodeArrayRule, OptionsNormalizer.unicodeArrayThresholdRule, OptionsNormalizer.encodeUnicodeLiteralsRule, OptionsNormalizer.selfDefendingRule];
+OptionsNormalizer.SOURCE_MAP_BASE_URL_OPTIONS = {
+    sourceMapMode: 'separate'
+};
+OptionsNormalizer.normalizerRules = [OptionsNormalizer.domainLockRule, OptionsNormalizer.unicodeArrayRule, OptionsNormalizer.unicodeArrayThresholdRule, OptionsNormalizer.encodeUnicodeLiteralsRule, OptionsNormalizer.sourceMapBaseUrl, OptionsNormalizer.selfDefendingRule];
 exports.OptionsNormalizer = OptionsNormalizer;
 
 /***/ },

+ 1 - 1
package.json

@@ -22,7 +22,7 @@
   "dependencies": {
     "babel-polyfill": "^6.13.0",
     "chance": "^1.0.4",
-    "class-validator": "^0.5.0",
+    "class-validator": "next",
     "commander": "^2.9.0",
     "escodegen": "^1.8.1",
     "esprima": "^3.0.0",

+ 4 - 0
src/JavaScriptObfuscatorInternal.ts

@@ -84,6 +84,10 @@ export class JavaScriptObfuscatorInternal {
      * @returns {IObfuscationResult}
      */
     public getObfuscationResult (): IObfuscationResult {
+        if (this.options.sourceMapBaseUrl) {
+            this.setSourceMapUrl(this.options.sourceMapBaseUrl);
+        }
+
         return new SourceMapCorrector(
             new ObfuscationResult(
                 this.generatorOutput.code,

+ 45 - 29
src/Utils.ts

@@ -62,6 +62,24 @@ export class Utils {
         return Number(dec).toString(radix);
     }
 
+    /**
+     * @param url
+     * @returns {string}
+     */
+    public static extractDomainFromUrl (url: string): string {
+        let domain: string;
+
+        if (url.indexOf('://') > -1 || url.indexOf('//') === 0) {
+            domain = url.split('/')[2];
+        } else {
+            domain = url.split('/')[0];
+        }
+
+        domain = domain.split(':')[0];
+
+        return domain;
+    }
+
     /**
      * @returns {Chance.Chance}
      */
@@ -71,7 +89,7 @@ export class Utils {
 
     /**
      * @param length
-     * @returns any
+     * @returns {string}
      */
     public static getRandomVariableName (length: number = 6): string {
         const rangeMinInteger: number = 10000,
@@ -89,42 +107,40 @@ export class Utils {
     }
 
     /**
+     * @param str
      * @param length
-     * @param charSet
-     * @returns string
+     * @returns {string[]}
      */
     public static hideString(str: string, length: number): [string, string] {
+        const escapeRegExp = (s: string) =>
+            s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
+
+        const randomMerge = function (s1: string, s2: string): string {
+            let i1 = -1, i2 = -1, result = '';
+
+            while (i1 < s1.length || i2 < s2.length) {
+                if (Math.random() < 0.5 && i2 < s2.length) {
+                    result += s2.charAt(++i2);
+                } else {
+                    result += s1.charAt(++i1);
+                }
+            }
 
-      // from http://stackoverflow.com/a/3561711
-      const escapeRegExp = (s: string) =>
-        s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
-
-      const randomMerge = function (s1: string, s2: string): string {
-        let i1 = -1, i2 = -1, result = '';
-        while (i1 < s1.length || i2 < s2.length) {
-          if (Math.random() < 0.5 && i2 < s2.length) {
-            result += s2.charAt(++i2);
-          } else {
-            result += s1.charAt(++i1);
-          }
-        }
-        return result;
-      }
+            return result;
+        };
 
-      // here we need a custom pool parameter because the default on from Change.string
-      // can return chars that break the RegExp
-      const customPool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
-      let randomString = Utils.randomGenerator.string({length: length, pool: customPool});
+        const customPool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
 
-      let randomStringDiff = randomString.replace(
-                              new RegExp('[' + escapeRegExp(str) + ']', 'g'),
-                              '');
+        let randomString = Utils.randomGenerator.string({length: length, pool: customPool}),
+            randomStringDiff = randomString.replace(
+                new RegExp('[' + escapeRegExp(str) + ']', 'g'),
+            ''),
+            randomStringDiffArray = randomStringDiff.split('');
 
-      let randomStringDiffArray = randomStringDiff.split('');
-      Utils.randomGenerator.shuffle(randomStringDiffArray);
-      randomStringDiff = randomStringDiffArray.join('');
+        Utils.randomGenerator.shuffle(randomStringDiffArray);
+        randomStringDiff = randomStringDiffArray.join('');
 
-      return [randomMerge(str, randomStringDiff), randomStringDiff];
+        return [randomMerge(str, randomStringDiff), randomStringDiff];
 
     }
 

+ 1 - 0
src/cli/JavaScriptObfuscatorCLI.ts

@@ -132,6 +132,7 @@ export class JavaScriptObfuscatorCLI {
             .option('--rotateUnicodeArray <boolean>', 'Disable rotation of unicode array values during obfuscation', JavaScriptObfuscatorCLI.parseBoolean)
             .option('--selfDefending <boolean>', 'Disables self-defending for obfuscated code', JavaScriptObfuscatorCLI.parseBoolean)
             .option('--sourceMap <boolean>', 'Enables source map generation', JavaScriptObfuscatorCLI.parseBoolean)
+            .option('--sourceMapBaseUrl <boolean>', 'Inserts custom Url for source map')
             .option(
                 '--sourceMapMode <string> [inline, separate]',
                 'Specify source map output mode',

+ 1 - 0
src/interfaces/IObfuscatorOptions.d.ts

@@ -10,6 +10,7 @@ export interface IObfuscatorOptions {
     rotateUnicodeArray?: boolean;
     selfDefending?: boolean;
     sourceMap?: boolean;
+    sourceMapBaseUrl?: string;
     sourceMapMode?: TSourceMapMode;
     unicodeArray?: boolean;
     unicodeArrayThreshold?: number;

+ 1 - 0
src/interfaces/IOptions.d.ts

@@ -10,6 +10,7 @@ export interface IOptions {
     readonly rotateUnicodeArray: boolean;
     readonly selfDefending: boolean;
     readonly sourceMap: boolean;
+    readonly sourceMapBaseUrl: string;
     readonly sourceMapMode: TSourceMapMode;
     readonly unicodeArray: boolean;
     readonly unicodeArrayThreshold: number;

+ 24 - 1
src/options/Options.ts

@@ -1,4 +1,16 @@
-import { IsBoolean, IsIn, IsNumber, IsString, Min, Max, validateSync, ValidationError, ValidatorOptions } from 'class-validator';
+import {
+    IsBoolean,
+    IsIn,
+    IsNumber,
+    IsString,
+    IsUrl,
+    Min,
+    Max,
+    ValidateIf,
+    validateSync,
+    ValidationError,
+    ValidatorOptions
+} from 'class-validator';
 
 import { IObfuscatorOptions } from "../interfaces/IObfuscatorOptions";
 import { IOptions } from "../interfaces/IOptions";
@@ -84,6 +96,17 @@ export class Options implements IOptions {
     @IsBoolean()
     public readonly sourceMap: boolean;
 
+    /**
+     * @type {boolean}
+     */
+    @IsString()
+    @ValidateIf((options: IOptions) => Boolean(options.sourceMapBaseUrl))
+    @IsUrl({
+        require_protocol: false,
+        require_valid_protocol: true
+    })
+    public readonly sourceMapBaseUrl: string;
+
     /**
      * @type {TSourceMapMode}
      */

+ 43 - 0
src/options/OptionsNormalizer.ts

@@ -3,6 +3,8 @@ import { IOptions } from "../interfaces/IOptions";
 
 import { TOptionsNormalizerRule } from "../types/TOptionsNormalizerRule";
 
+import { Utils } from "../Utils";
+
 export class OptionsNormalizer {
     /**
      * @type {IObfuscatorOptions}
@@ -31,13 +33,22 @@ export class OptionsNormalizer {
         selfDefending: true
     };
 
+    /**
+     * @type {IObfuscatorOptions}
+     */
+    private static SOURCE_MAP_BASE_URL_OPTIONS: IObfuscatorOptions = {
+        sourceMapMode: 'separate'
+    };
+
     /**
      * @type {TOptionsNormalizerRule[]}
      */
     private static normalizerRules: TOptionsNormalizerRule[] = [
+        OptionsNormalizer.domainLockRule,
         OptionsNormalizer.unicodeArrayRule,
         OptionsNormalizer.unicodeArrayThresholdRule,
         OptionsNormalizer.encodeUnicodeLiteralsRule,
+        OptionsNormalizer.sourceMapBaseUrl,
         OptionsNormalizer.selfDefendingRule
     ];
 
@@ -55,6 +66,26 @@ export class OptionsNormalizer {
         return normalizedOptions;
     }
 
+    /**
+     * @param options
+     * @returns {IOptions}
+     */
+    private static domainLockRule (options: IOptions): IOptions {
+        if (options.domainLock.length) {
+            let normalizedDomains: string[] = [];
+
+            for (let domain of options.domainLock) {
+                normalizedDomains.push(Utils.extractDomainFromUrl(domain));
+            }
+
+            Object.assign(options, {
+                domainLock: normalizedDomains
+            });
+        }
+
+        return options;
+    }
+
     /**
      * @param options
      * @returns {IOptions}
@@ -79,6 +110,18 @@ export class OptionsNormalizer {
         return options;
     }
 
+    /**
+     * @param options
+     * @returns {IOptions}
+     */
+    private static sourceMapBaseUrl (options: IOptions): IOptions {
+        if (options.sourceMapBaseUrl) {
+            Object.assign(options, OptionsNormalizer.SOURCE_MAP_BASE_URL_OPTIONS);
+        }
+
+        return options;
+    }
+
     /**
      * @param options
      * @returns {IOptions}

+ 1 - 0
src/preset-options/DefaultPreset.ts

@@ -13,6 +13,7 @@ export const DEFAULT_PRESET: IObfuscatorOptions = Object.freeze({
     rotateUnicodeArray: true,
     selfDefending: true,
     sourceMap: false,
+    sourceMapBaseUrl: '',
     sourceMapMode: SourceMapMode.Separate,
     unicodeArray: true,
     unicodeArrayThreshold: 0.8,

+ 1 - 0
src/preset-options/NoCustomNodesPreset.ts

@@ -13,6 +13,7 @@ export const NO_CUSTOM_NODES_PRESET: IObfuscatorOptions = Object.freeze({
     rotateUnicodeArray: false,
     selfDefending: false,
     sourceMap: false,
+    sourceMapBaseUrl: '',
     sourceMapMode: SourceMapMode.Separate,
     unicodeArray: false,
     unicodeArrayThreshold: 0,

+ 27 - 0
test/functional-tests/JavaScriptObfuscatorCLI.spec.ts

@@ -112,6 +112,33 @@ describe('JavaScriptObfuscatorCLI', function (): void {
                 assert.property(sourceMap, 'names');
             });
 
+            it('should creates file with source map in the same directory as output file if `sourceMapBaseUrl` is set', () => {
+                JavaScriptObfuscator.runCLI([
+                    'node',
+                    'javascript-obfuscator',
+                    fixtureFilePath,
+                    '--output',
+                    outputFilePath,
+                    '--compact',
+                    'true',
+                    '--selfDefending',
+                    '0',
+                    '--sourceMap',
+                    'true',
+                    '--sourceMapBaseUrl',
+                    'http://localhost:9000/'
+                ]);
+
+                assert.equal(fs.existsSync(outputSourceMapPath), true);
+
+                const content: string = fs.readFileSync(outputSourceMapPath, { encoding: 'utf8' }),
+                    sourceMap: any = JSON.parse(content);
+
+                assert.property(sourceMap, 'version');
+                assert.property(sourceMap, 'sources');
+                assert.property(sourceMap, 'names');
+            });
+
             afterEach(() => {
                 fs.unlinkSync(outputFilePath);
                 fs.unlinkSync(outputSourceMapPath);

+ 24 - 4
test/functional-tests/JavaScriptObfuscatorInternal.spec.ts

@@ -10,9 +10,10 @@ describe('JavaScriptObfuscatorInternal', () => {
     describe(`setSourceMapUrl (url: string)`, () => {
         let javaScriptObfuscator: JavaScriptObfuscatorInternal,
             obfuscationResult: IObfuscationResult,
-            sourceMapUrl: string = 'test.map.js';
+            sourceMapUrl: string;
 
-        beforeEach(() => {
+        it('should link obfuscated code with source map', () => {
+            sourceMapUrl = 'test.map.js';
             javaScriptObfuscator = new JavaScriptObfuscatorInternal(
                 `var test = 1;`,
                 Object.assign({}, NO_CUSTOM_NODES_PRESET, {
@@ -24,14 +25,33 @@ describe('JavaScriptObfuscatorInternal', () => {
             javaScriptObfuscator.setSourceMapUrl(sourceMapUrl);
 
             obfuscationResult = javaScriptObfuscator.getObfuscationResult();
-        });
 
-        it('should link obfuscated code with source map', () => {
             assert.match(
                 obfuscationResult.getObfuscatedCode(),
                 new RegExp(`sourceMappingURL=${sourceMapUrl}`))
             ;
             assert.isOk(JSON.parse(obfuscationResult.getSourceMap()).mappings);
         });
+
+        it('should properly add source map import to the obfuscated code if `sourceMapBaseUrl` is set', () => {
+            sourceMapUrl = 'http://localhost:9000/';
+            javaScriptObfuscator = new JavaScriptObfuscatorInternal(
+                `var test = 1;`,
+                Object.assign({}, NO_CUSTOM_NODES_PRESET, {
+                    sourceMap: true,
+                    sourceMapBaseUrl: sourceMapUrl
+                })
+            );
+
+            javaScriptObfuscator.obfuscate();
+
+            obfuscationResult = javaScriptObfuscator.getObfuscationResult();
+
+            assert.match(
+                obfuscationResult.getObfuscatedCode(),
+                new RegExp(`sourceMappingURL=${sourceMapUrl}$`))
+            ;
+            assert.isOk(JSON.parse(obfuscationResult.getSourceMap()).mappings);
+        });
     });
 });

+ 6 - 0
test/unit-tests/OptionsNormalizer.spec.ts

@@ -30,11 +30,14 @@ describe('OptionsNormalizer', () => {
             });
             optionsPreset2 = Object.assign({}, DEFAULT_PRESET, {
                 rotateUnicodeArray: true,
+                sourceMapBaseUrl: 'http://localhost:9000',
+                sourceMapMode: 'inline',
                 unicodeArray: true,
                 unicodeArrayThreshold: 0,
                 wrapUnicodeArrayCalls: true
             });
             optionsPreset3 = Object.assign({}, DEFAULT_PRESET, {
+                domainLock: ['//localhost:9000', 'https://google.ru/abc?cde=fgh'],
                 unicodeArray: true,
                 encodeUnicodeLiterals: true,
                 wrapUnicodeArrayCalls: false
@@ -49,11 +52,14 @@ describe('OptionsNormalizer', () => {
             });
             expectedOptionsPreset2 = Object.assign({}, DEFAULT_PRESET, {
                 rotateUnicodeArray: false,
+                sourceMapBaseUrl: 'http://localhost:9000',
+                sourceMapMode: 'separate',
                 unicodeArray: false,
                 unicodeArrayThreshold: 0,
                 wrapUnicodeArrayCalls: false
             });
             expectedOptionsPreset3 = Object.assign({}, DEFAULT_PRESET, {
+                domainLock: ['localhost', 'google.ru'],
                 unicodeArray: true,
                 encodeUnicodeLiterals: true,
                 wrapUnicodeArrayCalls: true

+ 10 - 0
test/unit-tests/Utils.spec.ts

@@ -50,6 +50,16 @@ describe('Utils', () => {
         });
     });
 
+    describe('extractDomainFromUrl (url: string): string', () => {
+        it('should extract domain from the given URL', () => {
+            assert.equal(Utils.extractDomainFromUrl('http://google.ru'), 'google.ru');
+            assert.equal(Utils.extractDomainFromUrl('http://www.google.ru'), 'www.google.ru');
+            assert.equal(Utils.extractDomainFromUrl('https://www.google.ru:9000'), 'www.google.ru');
+            assert.equal(Utils.extractDomainFromUrl('//google.ru/abc'), 'google.ru');
+            assert.equal(Utils.extractDomainFromUrl('//localhost:9000'), 'localhost');
+        });
+    });
+
     describe('getRandomVariableName (length: number = 6): string', () => {
         it('should return a string of given length with random variable name', () => {
             assert.match(Utils.getRandomVariableName(4), /^_0x(\w){4}$/);