html-minifier-next 5.1.3 → 5.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/htmlminifier.cjs
CHANGED
|
@@ -185,6 +185,23 @@ const preCompiledStackedTags = {
|
|
|
185
185
|
// Cache for compiled attribute regexes per handler configuration
|
|
186
186
|
const attrRegexCache = new WeakMap();
|
|
187
187
|
|
|
188
|
+
// O(n) helper: Strip all occurrences of `open…close` delimiters, keeping inner content
|
|
189
|
+
// Used instead of a regex replace to avoid O(n²) behavior on adversarial inputs
|
|
190
|
+
function stripDelimited(str, open, close) {
|
|
191
|
+
let result = '';
|
|
192
|
+
let i = 0;
|
|
193
|
+
while (i < str.length) {
|
|
194
|
+
const start = str.indexOf(open, i);
|
|
195
|
+
if (start === -1) { result += str.slice(i); break; }
|
|
196
|
+
result += str.slice(i, start);
|
|
197
|
+
const end = str.indexOf(close, start + open.length);
|
|
198
|
+
if (end === -1) { result += str.slice(start); break; }
|
|
199
|
+
result += str.slice(start + open.length, end);
|
|
200
|
+
i = end + close.length;
|
|
201
|
+
}
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
|
|
188
205
|
function buildAttrRegex(handler) {
|
|
189
206
|
let pattern = singleAttrIdentifier.source +
|
|
190
207
|
'(?:\\s*(' + joinSingleAttrAssigns(handler) + ')' +
|
|
@@ -256,9 +273,10 @@ class HTMLParser {
|
|
|
256
273
|
|
|
257
274
|
// Sticky regex versions for position-based matching (avoids string slicing)
|
|
258
275
|
const startTagOpenY = new RegExp(startTagOpen.source.slice(1), 'y');
|
|
276
|
+
// `\s*` with sticky flag is O(n) at worst—no retry from different positions possible
|
|
259
277
|
const startTagCloseY = /\s*(\/?)>/y;
|
|
260
278
|
const endTagY = new RegExp(endTag.source.slice(1), 'y');
|
|
261
|
-
const doctypeY = /<!DOCTYPE
|
|
279
|
+
const doctypeY = /<!DOCTYPE[^<>]+>/iy;
|
|
262
280
|
const commentTestY = /<!--/y;
|
|
263
281
|
const conditionalTestY = /<!\[/y;
|
|
264
282
|
|
|
@@ -444,9 +462,7 @@ class HTMLParser {
|
|
|
444
462
|
if (m && m.index === 0) {
|
|
445
463
|
let text = m[1];
|
|
446
464
|
if (stackedTag !== 'script' && stackedTag !== 'style' && stackedTag !== 'noscript') {
|
|
447
|
-
text = text
|
|
448
|
-
.replace(/<!--([\s\S]*?)-->/g, '$1')
|
|
449
|
-
.replace(/<!\[CDATA\[([\s\S]*?)]]>/g, '$1');
|
|
465
|
+
text = stripDelimited(stripDelimited(text, '<!--', '-->'), '<![CDATA[', ']]>');
|
|
450
466
|
}
|
|
451
467
|
if (handler.chars) {
|
|
452
468
|
const result = handler.chars(text);
|
|
@@ -3806,7 +3822,19 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
3806
3822
|
// Temporarily replace ignored chunks with comments, so that we don’t have to worry what’s there;
|
|
3807
3823
|
// for all we care there might be completely-horribly-broken-alien-non-html-emoji-cthulhu-filled content
|
|
3808
3824
|
if (value.indexOf('<!-- htmlmin:ignore -->') !== -1) {
|
|
3809
|
-
|
|
3825
|
+
// Use `indexOf`-based O(n) loop instead of a global regex with [\s\S]*? to avoid O(n²)
|
|
3826
|
+
// backtracking on adversarial HTML with many `<!--` prefixes but no closing marker
|
|
3827
|
+
const ignoreMarker = '<!-- htmlmin:ignore -->';
|
|
3828
|
+
const ignoreMarkerLen = ignoreMarker.length;
|
|
3829
|
+
let ignoreResult = '';
|
|
3830
|
+
let ignorePos = 0;
|
|
3831
|
+
while (ignorePos < value.length) {
|
|
3832
|
+
const ignoreStart = value.indexOf(ignoreMarker, ignorePos);
|
|
3833
|
+
if (ignoreStart === -1) { ignoreResult += value.slice(ignorePos); break; }
|
|
3834
|
+
ignoreResult += value.slice(ignorePos, ignoreStart);
|
|
3835
|
+
const ignoreEnd = value.indexOf(ignoreMarker, ignoreStart + ignoreMarkerLen);
|
|
3836
|
+
if (ignoreEnd === -1) { ignoreResult += value.slice(ignoreStart); break; }
|
|
3837
|
+
const group1 = value.slice(ignoreStart + ignoreMarkerLen, ignoreEnd);
|
|
3810
3838
|
if (!uidIgnore) {
|
|
3811
3839
|
uidIgnore = uniqueId(value);
|
|
3812
3840
|
const pattern = new RegExp('^' + uidIgnore + '([0-9]+)$');
|
|
@@ -3820,8 +3848,10 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
3820
3848
|
}
|
|
3821
3849
|
const token = '<!--' + uidIgnore + ignoredMarkupChunks.length + '-->';
|
|
3822
3850
|
ignoredMarkupChunks.push(group1);
|
|
3823
|
-
|
|
3824
|
-
|
|
3851
|
+
ignoreResult += token;
|
|
3852
|
+
ignorePos = ignoreEnd + ignoreMarkerLen;
|
|
3853
|
+
}
|
|
3854
|
+
value = ignoreResult;
|
|
3825
3855
|
}
|
|
3826
3856
|
|
|
3827
3857
|
// Create sort functions after `htmlmin:ignore` processing but before custom fragment UID markers
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"htmlminifier.d.ts","sourceRoot":"","sources":["../../src/htmlminifier.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"htmlminifier.d.ts","sourceRoot":"","sources":["../../src/htmlminifier.js"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"htmlparser.d.ts","sourceRoot":"","sources":["../../src/htmlparser.js"],"names":[],"mappings":"AAgDA,4BAAkE;
|
|
1
|
+
{"version":3,"file":"htmlparser.d.ts","sourceRoot":"","sources":["../../src/htmlparser.js"],"names":[],"mappings":"AAgDA,4BAAkE;AAwGlE;IACE,qCAGC;IAFC,UAAgB;IAChB,aAAsB;IAGxB,uBAmmBC;CACF"}
|
package/package.json
CHANGED
package/src/htmlminifier.js
CHANGED
|
@@ -931,7 +931,19 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
931
931
|
// Temporarily replace ignored chunks with comments, so that we don’t have to worry what’s there;
|
|
932
932
|
// for all we care there might be completely-horribly-broken-alien-non-html-emoji-cthulhu-filled content
|
|
933
933
|
if (value.indexOf('<!-- htmlmin:ignore -->') !== -1) {
|
|
934
|
-
|
|
934
|
+
// Use `indexOf`-based O(n) loop instead of a global regex with [\s\S]*? to avoid O(n²)
|
|
935
|
+
// backtracking on adversarial HTML with many `<!--` prefixes but no closing marker
|
|
936
|
+
const ignoreMarker = '<!-- htmlmin:ignore -->';
|
|
937
|
+
const ignoreMarkerLen = ignoreMarker.length;
|
|
938
|
+
let ignoreResult = '';
|
|
939
|
+
let ignorePos = 0;
|
|
940
|
+
while (ignorePos < value.length) {
|
|
941
|
+
const ignoreStart = value.indexOf(ignoreMarker, ignorePos);
|
|
942
|
+
if (ignoreStart === -1) { ignoreResult += value.slice(ignorePos); break; }
|
|
943
|
+
ignoreResult += value.slice(ignorePos, ignoreStart);
|
|
944
|
+
const ignoreEnd = value.indexOf(ignoreMarker, ignoreStart + ignoreMarkerLen);
|
|
945
|
+
if (ignoreEnd === -1) { ignoreResult += value.slice(ignoreStart); break; }
|
|
946
|
+
const group1 = value.slice(ignoreStart + ignoreMarkerLen, ignoreEnd);
|
|
935
947
|
if (!uidIgnore) {
|
|
936
948
|
uidIgnore = uniqueId(value);
|
|
937
949
|
const pattern = new RegExp('^' + uidIgnore + '([0-9]+)$');
|
|
@@ -945,8 +957,10 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
945
957
|
}
|
|
946
958
|
const token = '<!--' + uidIgnore + ignoredMarkupChunks.length + '-->';
|
|
947
959
|
ignoredMarkupChunks.push(group1);
|
|
948
|
-
|
|
949
|
-
|
|
960
|
+
ignoreResult += token;
|
|
961
|
+
ignorePos = ignoreEnd + ignoreMarkerLen;
|
|
962
|
+
}
|
|
963
|
+
value = ignoreResult;
|
|
950
964
|
}
|
|
951
965
|
|
|
952
966
|
// Create sort functions after `htmlmin:ignore` processing but before custom fragment UID markers
|
package/src/htmlparser.js
CHANGED
|
@@ -82,6 +82,23 @@ const preCompiledStackedTags = {
|
|
|
82
82
|
// Cache for compiled attribute regexes per handler configuration
|
|
83
83
|
const attrRegexCache = new WeakMap();
|
|
84
84
|
|
|
85
|
+
// O(n) helper: Strip all occurrences of `open…close` delimiters, keeping inner content
|
|
86
|
+
// Used instead of a regex replace to avoid O(n²) behavior on adversarial inputs
|
|
87
|
+
function stripDelimited(str, open, close) {
|
|
88
|
+
let result = '';
|
|
89
|
+
let i = 0;
|
|
90
|
+
while (i < str.length) {
|
|
91
|
+
const start = str.indexOf(open, i);
|
|
92
|
+
if (start === -1) { result += str.slice(i); break; }
|
|
93
|
+
result += str.slice(i, start);
|
|
94
|
+
const end = str.indexOf(close, start + open.length);
|
|
95
|
+
if (end === -1) { result += str.slice(start); break; }
|
|
96
|
+
result += str.slice(start + open.length, end);
|
|
97
|
+
i = end + close.length;
|
|
98
|
+
}
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
|
|
85
102
|
function buildAttrRegex(handler) {
|
|
86
103
|
let pattern = singleAttrIdentifier.source +
|
|
87
104
|
'(?:\\s*(' + joinSingleAttrAssigns(handler) + ')' +
|
|
@@ -153,9 +170,10 @@ export class HTMLParser {
|
|
|
153
170
|
|
|
154
171
|
// Sticky regex versions for position-based matching (avoids string slicing)
|
|
155
172
|
const startTagOpenY = new RegExp(startTagOpen.source.slice(1), 'y');
|
|
173
|
+
// `\s*` with sticky flag is O(n) at worst—no retry from different positions possible
|
|
156
174
|
const startTagCloseY = /\s*(\/?)>/y;
|
|
157
175
|
const endTagY = new RegExp(endTag.source.slice(1), 'y');
|
|
158
|
-
const doctypeY = /<!DOCTYPE
|
|
176
|
+
const doctypeY = /<!DOCTYPE[^<>]+>/iy;
|
|
159
177
|
const commentTestY = /<!--/y;
|
|
160
178
|
const conditionalTestY = /<!\[/y;
|
|
161
179
|
|
|
@@ -343,9 +361,7 @@ export class HTMLParser {
|
|
|
343
361
|
if (m && m.index === 0) {
|
|
344
362
|
let text = m[1];
|
|
345
363
|
if (stackedTag !== 'script' && stackedTag !== 'style' && stackedTag !== 'noscript') {
|
|
346
|
-
text = text
|
|
347
|
-
.replace(/<!--([\s\S]*?)-->/g, '$1')
|
|
348
|
-
.replace(/<!\[CDATA\[([\s\S]*?)]]>/g, '$1');
|
|
364
|
+
text = stripDelimited(stripDelimited(text, '<!--', '-->'), '<![CDATA[', ']]>');
|
|
349
365
|
}
|
|
350
366
|
if (handler.chars) {
|
|
351
367
|
const result = handler.chars(text);
|