Skip to content

Commit 8643ded

Browse files
committed
Added Option - contentIgnoreRegexes
1 parent 22323ab commit 8643ded

File tree

8 files changed

+110
-67
lines changed

8 files changed

+110
-67
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ It may not be the best setting but it works for me. :)
261261
|classSuffix|string|""|The suffix of the obfuscated class name.|
262262
|classIgnore|string[ ]|[ ]|The class names to be ignored during obfuscation.|
263263
|allowExtensions|string[ ]|[".jsx", ".tsx", ".js", ".ts", ".html", ".rsc"]|The file extensions to be processed.|
264+
|contentIgnoreRegexes|RegExp[ ]|[ ]|The regexes to match the content to be ignored during obfuscation.|
264265
|whiteListedFolderPaths|string[ ]|[ ]|The folder paths to be processed. Empty array means all folders will be processed.|
265266
|blackListedFolderPaths|string[ ]|[ ]|The folder paths to be ignored.|
266267
|includeAnyMatchRegexes|RegExp[ ]|[ ]|The regexes to match the file/folder paths to be processed.|
@@ -285,6 +286,7 @@ module.exports = {
285286
classSuffix: "", // Suffix of the obfuscated class name.
286287
classIgnore: [], // The class names to be ignored during obfuscation.
287288
allowExtensions: [".jsx", ".tsx", ".js", ".ts", ".html", ".rsc"], // The file extensions to be processed.
289+
contentIgnoreRegexes: [], // The regexes to match the file content to be ignored during obfuscation.
288290
289291
whiteListedFolderPaths: [], // Only obfuscate files in these folders
290292
blackListedFolderPaths: ["./.next/cache"], // Don't obfuscate files in these folders

docs/upgrade-to-v2.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ We have added a new individual configuration file `next-css-obfuscator.config.cj
2323
| srcPath | buildFolderPath |
2424
| desPath | buildFolderPath |
2525
| extensions | allowExtensions |
26+
| ➡️ | contentIgnoreRegexes |
2627
| formatJson ||
2728
| showConfig ||
2829
| keepData ||
@@ -37,4 +38,3 @@ We have added a new individual configuration file `next-css-obfuscator.config.cj
3738
| ➡️ | removeMarkersAfterObfuscated |
3839
| ➡️ | customTailwindDarkModeSelector |
3940
| ➡️ | logLevel |
40-

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "next-css-obfuscator",
3-
"version": "2.0.2",
3+
"version": "2.0.4",
44
"description": "A temporary solution for using postcss-obfuscator in Next.js.",
55
"main": "dist/index.js",
66
"type": "commonjs",

src/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const defaultOptions: Options = {
1212
classSuffix: "", // Suffix of the obfuscated class name.
1313
classIgnore: [], // The class names to be ignored during obfuscation.
1414
allowExtensions: [".jsx", ".tsx", ".js", ".ts", ".html", ".rsc"], // The file extensions to be processed.
15+
contentIgnoreRegexes: [], // The regexes to match the file content to be ignored during obfuscation.
1516

1617
whiteListedFolderPaths: [], // Only obfuscate files in these folders
1718
blackListedFolderPaths: ["./.next/cache"], // Don't obfuscate files in these folders

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ function obfuscate(options: Options) {
4949
allowExtensions: options.allowExtensions,
5050
classConversionJsonFolderPath: options.classConversionJsonFolderPath,
5151

52+
contentIgnoreRegexes: options.contentIgnoreRegexes,
53+
5254
whiteListedFolderPaths: options.whiteListedFolderPaths,
5355
blackListedFolderPaths: options.blackListedFolderPaths,
5456
includeAnyMatchRegexes: options.includeAnyMatchRegexes,

src/type.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type Options = {
1414
classSuffix: string;
1515
classIgnore: string[];
1616
allowExtensions: string[];
17+
contentIgnoreRegexes: RegExp[];
1718

1819
whiteListedFolderPaths: string[];
1920
blackListedFolderPaths: string[];
@@ -38,6 +39,7 @@ type OptionalOptions = {
3839
classSuffix?: string;
3940
classIgnore?: string[];
4041
allowExtensions?: string[];
42+
contentIgnoreRegexes: RegExp[];
4143

4244
whiteListedFolderPaths?: string[];
4345
blackListedFolderPaths?: string[];

src/utils.test.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -219,28 +219,28 @@ describe("copyCssData", () => {
219219
describe("findContentBetweenMarker", () => {
220220

221221
it("should return the correct content between markers", () => {
222-
const content = `123{45{67}8}901{2345678}9`;
222+
const content = `123{{4}5{67}8}901{2345678}9`;
223223
const targetStr = '5';
224224
const openSymbol = '{';
225225
const closeSymbol = '}';
226226

227-
const expectedOutput = ["45{67}8", "2345678"];
227+
const expectedOutput = ["{4}5{67}8", "2345678"];
228228

229229
const result = findContentBetweenMarker(content, targetStr, openSymbol, closeSymbol);
230230
expect(result).toEqual(expectedOutput);
231231
});
232232

233-
it('should return the correct content between (if marker length > 1)', () => {
234-
const content = '[Hello_0 [[Hello_1]]! Hello_2 [[Hello_3]]!]';
235-
const targetStr = 'He';
236-
const openSymbol = '[[';
237-
const closeSymbol = ']]';
233+
// it('should return the correct content between (if marker length > 1)', () => {
234+
// const content = '[Hello_0 [[Hello_1]]! Hello_2 [[Hello_3]]!]';
235+
// const targetStr = 'He';
236+
// const openSymbol = '[[';
237+
// const closeSymbol = ']]';
238238

239-
const expectedOutput = ['Hello_1', 'Hello_3'];
239+
// const expectedOutput = ['Hello_1', 'Hello_3'];
240240

241-
const result = findContentBetweenMarker(content, targetStr, openSymbol, closeSymbol);
242-
expect(result).toEqual(expectedOutput);
243-
});
241+
// const result = findContentBetweenMarker(content, targetStr, openSymbol, closeSymbol);
242+
// expect(result).toEqual(expectedOutput);
243+
// });
244244
});
245245

246246
//! ================================

src/utils.ts

Lines changed: 90 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ function replaceJsonKeysInFiles(
6060
allowExtensions,
6161
classConversionJsonFolderPath,
6262

63+
contentIgnoreRegexes,
64+
6365
whiteListedFolderPaths,
6466
blackListedFolderPaths,
6567
includeAnyMatchRegexes,
@@ -72,6 +74,8 @@ function replaceJsonKeysInFiles(
7274
allowExtensions: string[],
7375
classConversionJsonFolderPath: string,
7476

77+
contentIgnoreRegexes: RegExp[],
78+
7579
whiteListedFolderPaths: string[],
7680
blackListedFolderPaths: string[],
7781
includeAnyMatchRegexes: RegExp[],
@@ -111,19 +115,25 @@ function replaceJsonKeysInFiles(
111115
});
112116
}
113117
if (blackListedFolderPaths.length > 0) {
114-
isTargetFile = !blackListedFolderPaths.some((incloudPath) => {
118+
const res = !blackListedFolderPaths.some((incloudPath) => {
115119
return normalizePath(filePath).includes(normalizePath(incloudPath));
116120
});
121+
if (!res) {
122+
isTargetFile = false;
123+
}
117124
}
118125
if (includeAnyMatchRegexes.length > 0) {
119126
isTargetFile = includeAnyMatchRegexes.some((regex) => {
120127
return normalizePath(filePath).match(regex);
121128
});
122129
}
123130
if (excludeAnyMatchRegexes.length > 0) {
124-
isTargetFile = !excludeAnyMatchRegexes.some((regex) => {
131+
const res = !excludeAnyMatchRegexes.some((regex) => {
125132
return normalizePath(filePath).match(regex);
126133
});
134+
if (!res) {
135+
isTargetFile = false;
136+
}
127137
}
128138
if (!isTargetFile) {
129139
return;
@@ -146,7 +156,7 @@ function replaceJsonKeysInFiles(
146156
const htmlOriginal = html;
147157
const tagContents = findHtmlTagContentsByClass(html, obfuscateMarkerClass);
148158
tagContents.forEach(tagContent => {
149-
const { obfuscatedContent, usedKeys } = obfuscateKeys(classConversion, tagContent);
159+
const { obfuscatedContent, usedKeys } = obfuscateKeys(classConversion, tagContent, contentIgnoreRegexes);
150160
addKeysToRegistery(usedKeys);
151161
if (tagContent !== obfuscatedContent) {
152162
html = html.replace(tagContent, obfuscatedContent);
@@ -156,7 +166,7 @@ function replaceJsonKeysInFiles(
156166

157167
const scriptTagContents = findHtmlTagContents(html, "script");
158168
scriptTagContents.forEach(scriptTagContent => {
159-
const obfuscateScriptContent = obfuscateJs(scriptTagContent, obfuscateMarkerClass, classConversion, filePath);
169+
const obfuscateScriptContent = obfuscateJs(scriptTagContent, obfuscateMarkerClass, classConversion, filePath, contentIgnoreRegexes);
160170
if (scriptTagContent !== obfuscateScriptContent) {
161171
html = html.replace(scriptTagContent, obfuscateScriptContent);
162172
log("debug", `Obscured keys under HTML script tag in file:`, normalizePath(filePath));
@@ -168,7 +178,7 @@ function replaceJsonKeysInFiles(
168178
}
169179
}
170180
} else {
171-
const obfuscateScriptContent = obfuscateJs(fileContent, obfuscateMarkerClass, classConversion, filePath);
181+
const obfuscateScriptContent = obfuscateJs(fileContent, obfuscateMarkerClass, classConversion, filePath, contentIgnoreRegexes);
172182
if (fileContent !== obfuscateScriptContent) {
173183
fileContent = obfuscateScriptContent;
174184
log("debug", `Obscured keys in JS like content file:`, normalizePath(filePath));
@@ -177,9 +187,17 @@ function replaceJsonKeysInFiles(
177187

178188
});
179189
} else {
180-
const { obfuscatedContent, usedKeys } = obfuscateKeys(classConversion, fileContent);
181-
fileContent = obfuscatedContent;
182-
addKeysToRegistery(usedKeys);
190+
if ([".js"].includes(fileExt)) {
191+
const obfuscateScriptContent = obfuscateJs(fileContent, "jsx", classConversion, filePath, contentIgnoreRegexes);
192+
if (fileContent !== obfuscateScriptContent) {
193+
fileContent = obfuscateScriptContent;
194+
log("debug", `Obscured keys in JSX related file:`, normalizePath(filePath));
195+
}
196+
} else {
197+
const { obfuscatedContent, usedKeys } = obfuscateKeys(classConversion, fileContent, contentIgnoreRegexes);
198+
fileContent = obfuscatedContent;
199+
addKeysToRegistery(usedKeys);
200+
}
183201
}
184202

185203
if (fileContentOriginal !== fileContent) {
@@ -202,20 +220,40 @@ function replaceJsonKeysInFiles(
202220

203221
}
204222

205-
function obfuscateKeys(jsonData: ClassConversion, fileContent: string) {
223+
function obfuscateKeys(jsonData: ClassConversion, fileContent: string, contentIgnoreRegexes: RegExp[] = []) {
206224
//ref: https://github.com/n4j1Br4ch1D/postcss-obfuscator/blob/main/utils.js
207225

208226
const usedKeys = new Set<string>();
209227
Object.keys(jsonData).forEach((key) => {
210228
const fileContentOriginal = fileContent;
211229
let keyUse = escapeRegExp(key.slice(1).replace(/\\/g, ""));
212-
let regex;
213-
214230
//? sample: "text-sm w-full\n text-right\n p-2 flex gap-2 hover:bg-gray-100 dark:hover:bg-red-700 text-right"
215-
regex = new RegExp(`([\\s"'\\\`]|^)(${keyUse})(?=$|[\\s"'\\\`]|\\\\n)`, 'g'); // match exact wording & avoid ` ' ""
216-
// regex = new RegExp(`([\\s"'\\\`]|^)(${keyUse})(?=$|[\\s"'\\\`])`, 'g'); // match exact wording & avoid ` ' ""
231+
let exactMatchRegex = new RegExp(`([\\s"'\\\`]|^)(${keyUse})(?=$|[\\s"'\\\`]|\\\\n)`, 'g'); // match exact wording & avoid ` ' ""
232+
// exactMatchRegex = new RegExp(`([\\s"'\\\`]|^)(${keyUse})(?=$|[\\s"'\\\`])`, 'g'); // match exact wording & avoid ` ' ""
233+
234+
const replacement = `$1` + jsonData[key].slice(1).replace(/\\/g, "");
235+
236+
const matches = fileContent.match(exactMatchRegex);
237+
const originalObscuredContentPairs = matches?.map((match) => {
238+
return { originalContent: match, obscuredContent: match.replace(exactMatchRegex, replacement) };
239+
});
240+
fileContent = fileContent.replace(exactMatchRegex, replacement); // capture preceding space
241+
242+
if (contentIgnoreRegexes.length > 0) {
243+
contentIgnoreRegexes.forEach((regex) => {
244+
const originalContentFragments = fileContentOriginal.match(regex);
245+
246+
originalContentFragments?.map((originalContentFragment) => {
247+
originalObscuredContentPairs?.map((pair) => {
248+
if (originalContentFragments?.some((fragment) => fragment.includes(pair.originalContent))) {
249+
log("debug", "Obscured keys:", `Ignored ${pair.originalContent} at ${originalContentFragment}`);
250+
fileContent = fileContent.replace(originalContentFragment.replace(pair.originalContent, pair.obscuredContent), originalContentFragment);
251+
}
252+
});
253+
});
254+
});
255+
}
217256

218-
fileContent = fileContent.replace(regex, `$1` + jsonData[key].slice(1).replace(/\\/g, "")); // capture preceding space
219257

220258
if (fileContentOriginal !== fileContent && !usedKeys.has(key)) {
221259
usedKeys.add(key);
@@ -375,60 +413,57 @@ function findContentBetweenMarker(content: string, targetStr: string, openMarker
375413
if (openMarker === closeMarker) {
376414
throw new Error("openMarker and closeMarker can not be the same");
377415
}
416+
// not support multi char marker
417+
if (openMarker.length > 1 || closeMarker.length > 1) {
418+
throw new Error("openMarker and closeMarker can only be one character");
419+
}
378420

379-
const foundContents: string[] = [];
380-
381-
let targetStrPosition = content.indexOf(targetStr);
382-
while (targetStrPosition !== -1 && targetStrPosition < content.length) {
383-
let openMarkerPosition = -1;
421+
let lastEndIndex = 0;
422+
let results: string[] = [];
384423

385-
// search the closer openMarker before the targetStr
386-
let currentPosition = targetStrPosition;
387-
while (openMarkerPosition === -1 && currentPosition > 0) {
388-
const reading = content.slice(currentPosition, currentPosition + openMarker.length);
389-
if (reading === openMarker) {
390-
openMarkerPosition = currentPosition;
391-
} else if (reading === closeMarker) {
392-
break;
393-
} else {
394-
currentPosition--;
395-
}
424+
while (true) {
425+
const targetIndex = content.indexOf(targetStr, lastEndIndex);
426+
if (targetIndex === -1) {
427+
break;
396428
}
397-
if (openMarkerPosition !== -1) {
398-
let closeMarkerPosition = openMarkerPosition;
399-
if (closeMarkerPosition < content.length) {
400-
let count = 1;
401-
let closeMarkerReadingFramePos = closeMarkerPosition + closeMarker.length;
402-
while (count > 0 && closeMarkerReadingFramePos < content.length) {
403-
if (content.slice(closeMarkerReadingFramePos, closeMarkerReadingFramePos + openMarker.length) === openMarker) {
404-
count++;
405-
closeMarkerReadingFramePos += openMarker.length;
406-
} else if (content.slice(closeMarkerReadingFramePos, closeMarkerReadingFramePos + closeMarker.length) === closeMarker) {
407-
count--;
408-
closeMarkerReadingFramePos += closeMarker.length;
409-
} else {
410-
closeMarkerReadingFramePos++;
411-
}
429+
430+
let level = 0;
431+
let openMarkerIndex = -1;
432+
let closeMarkerIndex = -1;
433+
434+
for (let i = 0; i < content.length; i++) {
435+
if (content.substring(i, i + openMarker.length) === openMarker) {
436+
if (i < targetIndex && (openMarkerIndex === -1 || level === 0)) {
437+
openMarkerIndex = i;
412438
}
413-
if (count === 0) {
414-
const block = content.slice(openMarkerPosition + openMarker.length, closeMarkerReadingFramePos - closeMarker.length);
415-
if (block.includes(targetStr)) {
416-
foundContents.push(block);
417-
}
439+
level++;
440+
i += openMarker.length - 1; // Skip the characters we've just checked
441+
} else if (content.substring(i, i + closeMarker.length) === closeMarker) {
442+
level--;
443+
if (level === 0 && i > targetIndex) {
444+
closeMarkerIndex = i;
445+
break;
418446
}
447+
i += closeMarker.length - 1; // Skip the characters we've just checked
419448
}
420449
}
421-
targetStrPosition = content.indexOf(targetStr, targetStrPosition + 1);
450+
451+
if (openMarkerIndex === -1 || closeMarkerIndex === -1) {
452+
break;
453+
}
454+
455+
results.push(content.substring(openMarkerIndex + openMarker.length, closeMarkerIndex));
456+
lastEndIndex = closeMarkerIndex + closeMarker.length;
422457
}
423458

424-
return foundContents;
459+
return results;
425460
}
426461

427462
function obfuscateJs(content: string, key: string, jsonData: ClassConversion
428-
, filePath: string) {
463+
, filePath: string, contentIgnoreRegexes: RegExp[] = []) {
429464
const truncatedContents = findContentBetweenMarker(content, key, "{", "}");
430465
truncatedContents.forEach((truncatedContent) => {
431-
const { obfuscatedContent, usedKeys } = obfuscateKeys(jsonData, truncatedContent);
466+
const { obfuscatedContent, usedKeys } = obfuscateKeys(jsonData, truncatedContent, contentIgnoreRegexes);
432467
addKeysToRegistery(usedKeys);
433468
if (truncatedContent !== obfuscatedContent) {
434469
content = content.replace(truncatedContent, obfuscatedContent);
@@ -784,4 +819,5 @@ export {
784819
, replaceJsonKeysInFiles, setLogLevel
785820
, copyCssData, findContentBetweenMarker, findHtmlTagContentsByClass
786821
, findAllFilesWithExt, createClassConversionJson, extractClassFromSelector
822+
, obfuscateKeys
787823
};

0 commit comments

Comments
 (0)