node_modules: update (#325)

Co-authored-by: dawidd6 <9713907+dawidd6@users.noreply.github.com>
This commit is contained in:
Dawid Dziurla
2026-09-25 18:42:41 +02:00
committed by GitHub
parent bd35283f19
commit 6d91308e46
19 changed files with 300 additions and 79 deletions
+53 -8
View File
@@ -88,6 +88,29 @@ function _isWordCode(code) {
function _isBoundary(text, at) {
return _isWordCode(text.charCodeAt(at - 1)) !== _isWordCode(text.charCodeAt(at));
}
/**
* Offset of the first '@' in `text` between `from` and `to`, or -1 when the range holds none.
*
* indexOf would scan on to the end of the value, and the value here is a whole header. The
* walk below steps one whitespace delimited run at a time and only ever uses a '@' that sits
* inside the run it is on, so an unbounded probe rescans everything behind that run once per
* run and grows with the square of the header: 400KB of free text carrying no '@' took a
* quarter of a second. GHSA-v53p-9fqp-m79j took the pattern search out of this walk and left
* the probe unbounded behind it.
*
* @param text Text to look in
* @param from Offset to start at
* @param to Offset to stop before
* @return Offset of the '@', or -1
*/
function _indexOfAt(text, from, to) {
for (let i = from; i < to; i++) {
if (text.charCodeAt(i) === 0x40) {
return i;
}
}
return -1;
}
/**
* Finds the offset LOOSE_TEXT_ADDR matches at, or -1 when it does not match at all.
*
@@ -118,8 +141,8 @@ function _looseAddressStart(text) {
while (runEnd < len && !_isSpaceCode(text.charCodeAt(runEnd))) {
runEnd++;
}
let at = text.indexOf('@', runStart);
if (at >= 0 && at < runEnd) {
let at = _indexOfAt(text, runStart, runEnd);
if (at >= 0) {
let lastBoundary = -1;
for (let k = runEnd; k > runStart; k--) {
if (_isBoundary(text, k)) {
@@ -128,7 +151,7 @@ function _looseAddressStart(text) {
}
}
let atomStart = runStart;
while (lastBoundary >= 0 && at >= 0 && at < runEnd) {
while (lastBoundary >= 0 && at >= 0) {
// '[^@\s]+' has to cover a character before the '@' and '[^\s]+' one after it,
// and the boundary that ends the match has to sit past both
if (at > atomStart && runEnd > at + 1 && lastBoundary > at + 1) {
@@ -148,7 +171,7 @@ function _looseAddressStart(text) {
}
}
atomStart = at + 1;
at = text.indexOf('@', atomStart);
at = _indexOfAt(text, atomStart, runEnd);
}
}
pos = runEnd;
@@ -275,6 +298,19 @@ function _handleAddress(tokens, depth) {
}
}
else if (token.value) {
// An empty quoted string is dropped by the tokenizer, leaving no text token of its
// own for textWasQuoted to be recorded on, so the pair of quote operators right in
// front of this token is all that is left of it and the run it opens carries the
// quoting instead. Without this '""@example.com' reads as the bare '@example.com',
// the quotes never go back on, and the value is no longer an addr-spec a trailing
// comment can be peeled off of. It only ever opens a run: a run that already holds
// material collected outside the quotes is not a quoted string, whatever follows it
const prevPrevToken = i > 1 ? tokens[i - 2] : null;
const opensAfterEmptyQuotedString = prevToken?.type === 'operator' &&
prevToken.value === '"' &&
!!prevToken.noBreak &&
prevPrevToken?.type === 'operator' &&
prevPrevToken.value === '"';
if (state === 'address') {
// Handle unquoted name that includes a "<".
// Apple Mail truncates everything between an unexpected < and an address.
@@ -302,7 +338,7 @@ function _handleAddress(tokens, depth) {
data[state].push(token.value);
lastChars[state] = token.value.charAt(token.value.length - 1);
if (state === 'text') {
data.textWasQuoted.push(insideQuotes);
data.textWasQuoted.push(insideQuotes || opensAfterEmptyQuotedString);
}
}
}
@@ -387,6 +423,18 @@ function _handleAddress(tokens, depth) {
// Join values with spaces
data.text = data.text.join(' ');
data.address = data.address.join(' ');
if (addressFromQuotedText && data.text) {
// The mailbox is still sitting in the text, so it moves over here and is quoted
// before the recovery below rather than after it. Anything else the text holds
// came along with it: a comment ends the domain but leaves the atoms behind it in
// the same text, and '"user"@example.com(x)evil.com' was handed on as the address
// 'user@example.com evil.com', a second domain riding into the envelope recipient
// on a value that is no addr-spec at all (GHSA-g57g-f23g-4646). Putting the quotes
// back first is what lets the recovery tell the whitespace an addr-spec may carry
// from the wreckage trailing one, as only a quoted local part may hold whitespace
data.address = _quoteLocalPart(data.text);
data.text = '';
}
_recoverAddrSpec(data);
const address = {
address: data.address || data.text || '',
@@ -400,9 +448,6 @@ function _handleAddress(tokens, depth) {
address.address = '';
}
}
if (addressFromQuotedText && address.address) {
address.address = _quoteLocalPart(address.address);
}
addresses.push(address);
}
return addresses;