diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dd8948a..9837cce8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Change Log v5.4.7 --- +* Fixed directory obfuscation with a set `sourceMapFileName` making all files share and overwrite one `.map`. Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/817 * Fixed CLI `--config` failures hiding the real cause behind a generic `Cannot open config file` message. Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1101 * Fixed `sourceMapFileName` ending in `.js.map` (e.g. `foo.min.js.map`) being mangled in the emitted `//# sourceMappingURL=` comment. Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1312 * Fixed `URIError: URI malformed` crash when `stringArray` with `base64`/`rc4` encoding processed a string literal containing lone surrogate code units (e.g. `"[^\uD800-\uDFFF]"`). Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1431 diff --git a/src/cli/utils/ObfuscatedCodeFileUtils.ts b/src/cli/utils/ObfuscatedCodeFileUtils.ts index 443f225d..1f421362 100644 --- a/src/cli/utils/ObfuscatedCodeFileUtils.ts +++ b/src/cli/utils/ObfuscatedCodeFileUtils.ts @@ -88,6 +88,8 @@ export class ObfuscatedCodeFileUtils { throw new Error('Output code path is empty'); } + sourceMapFileName = this.getUniqueSourceMapFileName(outputCodePath, sourceMapFileName); + let normalizedOutputCodePath: string = path.normalize(outputCodePath); let parsedOutputCodePath: path.ParsedPath = path.parse(normalizedOutputCodePath); @@ -140,4 +142,37 @@ export class ObfuscatedCodeFileUtils { encoding: JavaScriptObfuscatorCLI.encoding }); } + + /** + * For directory obfuscation a single `sourceMapFileName` would be shared by every file and the + * source maps would overwrite each other, so it is prefixed with the output code file name to + * keep each source map unique. + * https://github.com/javascript-obfuscator/javascript-obfuscator/issues/817 + * + * @param {string} outputCodePath + * @param {string} sourceMapFileName + * @returns {string} + */ + private getUniqueSourceMapFileName(outputCodePath: string, sourceMapFileName: string): string { + if (!sourceMapFileName || !this.isDirectoryInputPath()) { + return sourceMapFileName; + } + + const outputCodeName: string = path.parse(outputCodePath).name; + const parsedSourceMapFileName: path.ParsedPath = path.parse(sourceMapFileName); + + // keep any leading directory part of `sourceMapFileName`, prefix only its file name + return path.join(parsedSourceMapFileName.dir, `${outputCodeName}-${parsedSourceMapFileName.base}`); + } + + /** + * @returns {boolean} + */ + private isDirectoryInputPath(): boolean { + try { + return fs.lstatSync(this.inputPath).isDirectory(); + } catch { + return false; + } + } } diff --git a/test/unit-tests/cli/utils/ObfuscatedCodeFileUtils.spec.ts b/test/unit-tests/cli/utils/ObfuscatedCodeFileUtils.spec.ts index db0fac26..14672dc8 100644 --- a/test/unit-tests/cli/utils/ObfuscatedCodeFileUtils.spec.ts +++ b/test/unit-tests/cli/utils/ObfuscatedCodeFileUtils.spec.ts @@ -715,6 +715,49 @@ describe('obfuscatedCodeFileUtils', () => { }); }); + // https://github.com/javascript-obfuscator/javascript-obfuscator/issues/817 + describe('Variant #2: input path is a directory', () => { + const rawInputPath: string = path.join(tmpDirectoryPath, 'sm-input'); + const rawOutputPath: string = path.join(tmpDirectoryPath, 'output'); + const sourceMapFileName: string = 'map'; + + let firstOutputSourceMapPath: string; + let secondOutputSourceMapPath: string; + + before(() => { + fs.mkdirSync(rawInputPath, { recursive: true }); + + const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(rawInputPath, { + output: rawOutputPath + }); + + firstOutputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath( + path.join(rawOutputPath, 'foo.js'), + sourceMapFileName + ); + secondOutputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath( + path.join(rawOutputPath, 'bar.js'), + sourceMapFileName + ); + }); + + after(() => { + rimraf.sync(rawInputPath); + }); + + it('match #1: should prefix the source map file name with the output code file name', () => { + assert.equal(firstOutputSourceMapPath, path.join(rawOutputPath, 'foo-map.js.map')); + }); + + it('match #2: should prefix the source map file name with the output code file name', () => { + assert.equal(secondOutputSourceMapPath, path.join(rawOutputPath, 'bar-map.js.map')); + }); + + it('should produce a unique source map path per file', () => { + assert.notEqual(firstOutputSourceMapPath, secondOutputSourceMapPath); + }); + }); + describe('Variant #3: empty paths', () => { const rawInputPath: string = path.join(tmpDirectoryPath, 'input', 'test-input.js'); const rawOutputPath: string = path.join(tmpDirectoryPath, 'output', 'test-output.js');