Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
Change Log

v5.5.1
---
* Fixed leading whitespace of the source code being stripped without being reflected in the source map, shifting all `sourcesContent` and original position mappings. Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1437

v5.5.0
---
* Pro API: reworked large file uploads — fixed `413 Content Too Large` for ~4.4–4.6MB request bodies, and Blob uploads now send the raw source (`blobFormat: 'raw'`) instead of the JSON request body, so uploads always fit the plan's file size cap
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "javascript-obfuscator",
"version": "5.5.0",
"version": "5.5.1",
"description": "JavaScript obfuscator",
"keywords": [
"obfuscator",
Expand Down Expand Up @@ -89,6 +89,7 @@
"prettier": "3.6.2",
"rimraf": "6.0.1",
"sinon": "19.0.2",
"source-map": "0.6.1",
"source-map-resolve": "0.6.0",
"source-map-support": "0.5.21",
"terser": "5.44.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ import { AbstractCodeTransformer } from '../AbstractCodeTransformer';
@injectFromBase()
@injectable()
export class HashbangOperatorTransformer extends AbstractCodeTransformer {
/**
* @type {RegExp}
*/
private static readonly hashbangOperatorRegExp: RegExp = /^(\s*)#!.*(?:\r?\n)*/;

/**
* @type {string | null}
*/
Expand Down Expand Up @@ -52,15 +57,22 @@ export class HashbangOperatorTransformer extends AbstractCodeTransformer {
* @returns {string}
*/
private removeAndSaveHashbangOperatorLine(code: string): string {
return code
.replace(/^#!.*$(\r?\n)*/m, (substring: string) => {
if (substring) {
this.hashbangOperatorLine = substring;
}

return '';
})
.trim();
const match: RegExpMatchArray | null = code.match(HashbangOperatorTransformer.hashbangOperatorRegExp);

if (!match) {
return code;
}

const [hashbangOperatorSubstring, leadingWhitespacesSubstring] = match;

// an indented hashbang operator is an invalid one, so it is removed without being appended back
const isValidHashbangOperator: boolean = !/[^\r\n]/.test(leadingWhitespacesSubstring);

if (isValidHashbangOperator) {
this.hashbangOperatorLine = hashbangOperatorSubstring.slice(leadingWhitespacesSubstring.length);
}

return code.slice(hashbangOperatorSubstring.length);
}

/**
Expand Down
106 changes: 106 additions & 0 deletions test/functional-tests/issues/issue1437.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { assert } from 'chai';
import { RawSourceMap, SourceMapConsumer } from 'source-map';

import { NO_ADDITIONAL_NODES_PRESET } from '../../../src/options/presets/NoCustomNodes';

import { ISourceMap } from '../../../src/interfaces/source-code/ISourceMap';

import { SourceMapMode } from '../../../src/enums/source-map/SourceMapMode';
import { SourceMapSourcesMode } from '../../../src/enums/source-map/SourceMapSourcesMode';

import { JavaScriptObfuscator } from '../../../src/JavaScriptObfuscatorFacade';

//
// https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1437
//
describe('Issue #1437', () => {
interface IPosition {
line: number;
column: number;
}

/**
* @param {string} code
* @param {string} token
* @returns {IPosition}
*/
const positionOf = (code: string, token: string): IPosition => {
const linesBeforeToken: string[] = code.slice(0, code.indexOf(token)).split('\n');

return {
line: linesBeforeToken.length,
column: linesBeforeToken[linesBeforeToken.length - 1].length
};
};

describe('Source map of the code with a leading whitespace', () => {
describe('Variant #1: leading new line', () => {
const code: string = '\nthrow new Error("boom");\n';

let obfuscatedCode: string, sourceMap: ISourceMap;

before(() => {
const obfuscationResult = JavaScriptObfuscator.obfuscate(code, {
...NO_ADDITIONAL_NODES_PRESET,
compact: false,
sourceMap: true,
sourceMapMode: SourceMapMode.Separate,
sourceMapSourcesMode: SourceMapSourcesMode.SourcesContent,
seed: 1
});

obfuscatedCode = obfuscationResult.getObfuscatedCode();
sourceMap = JSON.parse(obfuscationResult.getSourceMap());
});

it('should keep the original source code inside the `sourcesContent` field', () => {
assert.deepEqual(sourceMap.sourcesContent, [code]);
});

it('should map generated token to its position inside the original source code', () => {
const consumer: SourceMapConsumer = new SourceMapConsumer(<RawSourceMap>(<unknown>sourceMap));
const originalPosition = consumer.originalPositionFor(positionOf(obfuscatedCode, 'Error'));

assert.deepEqual(
{ line: originalPosition.line, column: originalPosition.column },
positionOf(code, 'Error')
);
});
});

describe('Variant #2: leading and trailing whitespaces', () => {
const code: string = '\n\n var foo = 1;\n\n';

let obfuscatedCode: string, sourceMap: ISourceMap;

before(() => {
const obfuscationResult = JavaScriptObfuscator.obfuscate(code, {
...NO_ADDITIONAL_NODES_PRESET,
compact: false,
renameGlobals: true,
sourceMap: true,
sourceMapMode: SourceMapMode.Separate,
sourceMapSourcesMode: SourceMapSourcesMode.SourcesContent,
seed: 1
});

obfuscatedCode = obfuscationResult.getObfuscatedCode();
sourceMap = JSON.parse(obfuscationResult.getSourceMap());
});

it('should keep the original source code inside the `sourcesContent` field', () => {
assert.deepEqual(sourceMap.sourcesContent, [code]);
});

it('should map generated token to its position inside the original source code', () => {
const consumer: SourceMapConsumer = new SourceMapConsumer(<RawSourceMap>(<unknown>sourceMap));
const originalPosition = consumer.originalPositionFor(positionOf(obfuscatedCode, 'var'));

assert.deepEqual(
{ line: originalPosition.line, column: originalPosition.column },
positionOf(code, 'var')
);
});
});
});
});
1 change: 1 addition & 0 deletions test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ import './functional-tests/issues/issue419.spec';
import './functional-tests/issues/issue424.spec';
import './functional-tests/issues/issue437.spec';
import './functional-tests/issues/issue1419.spec';
import './functional-tests/issues/issue1437.spec';
import './functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec';
import './functional-tests/node-transformers/control-flow-transformers/block-statement-control-flow-transformer/BlockStatementControlFlowTransformer.spec';
import './functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/binary-expression-control-flow-replacer/BinaryExpressionControlFlowReplacer.spec';
Expand Down
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4871,7 +4871,7 @@ source-map-support@~0.5.20:
buffer-from "^1.0.0"
source-map "^0.6.0"

source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
version "0.6.1"
resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
Expand Down
Loading