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
2 changes: 1 addition & 1 deletion .licenses/npm/fast-xml-parser.dep.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 7 additions & 4 deletions __tests__/distributors/jetbrains-installer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import type {IncomingMessage} from 'http';
import {Readable} from 'stream';

import manifestData from '../data/jetbrains.json' with {type: 'json'};
import os from 'os';

// Mock @actions/core before importing source modules that depend on it
jest.unstable_mockModule('@actions/core', () => ({
Expand Down Expand Up @@ -81,6 +80,7 @@ describe('getAvailableVersions', () => {
jest.setTimeout(10_000);

let spyHttpClient: any;
let spyHttpClientHead: any;
let spyCoreError: any;
const originalGitHubToken = process.env.GITHUB_TOKEN;

Expand All @@ -93,6 +93,10 @@ describe('getAvailableVersions', () => {
headers: {},
result: []
});
spyHttpClientHead = jest.spyOn(HttpClient.prototype, 'head');
spyHttpClientHead.mockResolvedValue({
message: {statusCode: 200}
} as any);

// Mock core.error to suppress error logs
spyCoreError = core.error as jest.Mock;
Expand Down Expand Up @@ -133,9 +137,7 @@ describe('getAvailableVersions', () => {
const availableVersions = await distribution['getAvailableVersions']();
expect(availableVersions).not.toBeNull();

const length =
os.platform() === 'win32' ? manifestData.length : manifestData.length + 2;
expect(availableVersions.length).toBe(length);
expect(availableVersions.length).toBe(manifestData.length + 2);
}, 10_000);

it('continues a stable request after an all-prerelease page', async () => {
Expand Down Expand Up @@ -358,6 +360,7 @@ describe('getAvailableVersions', () => {

it('retries a GitHub rate limit using Retry-After', async () => {
spyHttpClient.mockRestore();
spyHttpClientHead.mockRestore();
const sleep = jest.fn(async () => undefined);
const requestRaw = jest
.spyOn(HttpClient.prototype, 'requestRaw')
Expand Down
102 changes: 97 additions & 5 deletions dist/cleanup/767.index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18360,9 +18360,102 @@ function readAttributeStr(xmlData, i) {
}

/**
* Select all the attributes whether valid or invalid.
*/
const validAttrStrRegxp = new RegExp('(\\s*)([^\\s=]+)(\\s*=)?(\\s*([\'"])(([\\s\\S])*?)\\5)?', 'g');
* Walk `attrStr` once, left to right, splitting it into attribute tokens.
*
* This replaces a regex that used to do the same job
* (`(\s*)([^\s=]+)(\s*=)?(\s*(['"])(([\s\S])*?)\5)?`). That regex led with an
* optional whitespace group followed by a required "non-whitespace" group.
* On a long run of whitespace that never resolves into an attribute name
* (e.g. a tag with thousands of trailing spaces before `>`), the engine
* backtracks the whitespace group one character at a time before giving up
* and moving to the next starting position — one full backtrack per
* position, which is quadratic in the length of the run.
*
* A single forward-only scan can never backtrack, so it can't be made slow
* this way no matter how much whitespace the input contains — it's always
* proportional to the length of the string, once.
*
* Each returned token mirrors the shape the old regex match array had, so
* the validation logic below (which reads token[1]..token[6]) didn't need
* to change:
* token.startIndex - where this token begins in attrStr
* token[1] - leading whitespace before the name
* token[2] - the attribute name
* token[3] - whitespace + '=' if present, else undefined
* token[4] - marker (any defined value) if a quoted value was found
* token[5] - the quote character used ('"' or "'")
* token[6] - the value's text, without the surrounding quotes
*
* A malformed leading character (e.g. a stray '=' with no name before it)
* is simply skipped over, one character at a time — the same outcome the
* old regex produced by failing to match at that position and retrying at
* the next one.
*/
function scanAttributeTokens(attrStr) {
const tokens = [];
const len = attrStr.length;
let i = 0;

while (i < len) {
const tokenStart = i;

// Leading whitespace before the name.
while (i < len && isWhiteSpace(attrStr[i])) i++;
if (i >= len) break; // trailing whitespace only — nothing left to read

if (attrStr[i] === '=') {
// No name before this '=' — not a valid attribute start. Move past
// just this one character and try again from the next position.
i = tokenStart + 1;
continue;
}

const leadingWs = attrStr.slice(tokenStart, i);

// Attribute name — everything up to the next whitespace or '='.
const nameStart = i;
while (i < len && !isWhiteSpace(attrStr[i]) && attrStr[i] !== '=') i++;
const name = attrStr.slice(nameStart, i);

// Optional whitespace + '='.
let equalsGroup; // whitespace + '=' text, or undefined if absent
let j = i;
while (j < len && isWhiteSpace(attrStr[j])) j++;
if (j < len && attrStr[j] === '=') {
equalsGroup = attrStr.slice(i, j + 1);
i = j + 1;
}

// Optional whitespace + quoted value.
let quoteChar;
let value;
let k = i;
while (k < len && isWhiteSpace(attrStr[k])) k++;
if (k < len && (attrStr[k] === '"' || attrStr[k] === "'")) {
const valueStart = k + 1;
const closeIdx = attrStr.indexOf(attrStr[k], valueStart);
if (closeIdx !== -1) {
quoteChar = attrStr[k];
value = attrStr.slice(valueStart, closeIdx);
i = closeIdx + 1;
}
// No closing quote found anywhere in the rest of the string — leave
// quoteChar/value undefined, same as the old regex's group failing
// to match a backreference-less run.
}

const token = { startIndex: tokenStart };
token[1] = leadingWs;
token[2] = name;
token[3] = equalsGroup;
token[4] = quoteChar !== undefined ? true : undefined;
token[5] = quoteChar;
token[6] = value;
tokens.push(token);
}

return tokens;
}

//attr, ="sd", a="amit's", a="sd"b="saf", ab cd=""

Expand All @@ -18371,7 +18464,7 @@ function validateAttributeString(attrStr, options) {

//if(attrStr.trim().length === 0) return true; //empty string

const matches = getAllMatches(attrStr, validAttrStrRegxp);
const matches = scanAttributeTokens(attrStr);
const attrNames = {};

for (let i = 0; i < matches.length; i++) {
Expand Down Expand Up @@ -18473,7 +18566,6 @@ function getLineNumberForPosition(xmlData, index) {
function getPositionFromMatch(match) {
return match.startIndex + match[1].length;
}

;// CONCATENATED MODULE: ./node_modules/fast-xml-parser/src/fxp.js


Expand Down
100 changes: 96 additions & 4 deletions dist/setup/824.index.js
Original file line number Diff line number Diff line change
Expand Up @@ -418,9 +418,102 @@ function readAttributeStr(xmlData, i) {
}

/**
* Select all the attributes whether valid or invalid.
* Walk `attrStr` once, left to right, splitting it into attribute tokens.
*
* This replaces a regex that used to do the same job
* (`(\s*)([^\s=]+)(\s*=)?(\s*(['"])(([\s\S])*?)\5)?`). That regex led with an
* optional whitespace group followed by a required "non-whitespace" group.
* On a long run of whitespace that never resolves into an attribute name
* (e.g. a tag with thousands of trailing spaces before `>`), the engine
* backtracks the whitespace group one character at a time before giving up
* and moving to the next starting position — one full backtrack per
* position, which is quadratic in the length of the run.
*
* A single forward-only scan can never backtrack, so it can't be made slow
* this way no matter how much whitespace the input contains — it's always
* proportional to the length of the string, once.
*
* Each returned token mirrors the shape the old regex match array had, so
* the validation logic below (which reads token[1]..token[6]) didn't need
* to change:
* token.startIndex - where this token begins in attrStr
* token[1] - leading whitespace before the name
* token[2] - the attribute name
* token[3] - whitespace + '=' if present, else undefined
* token[4] - marker (any defined value) if a quoted value was found
* token[5] - the quote character used ('"' or "'")
* token[6] - the value's text, without the surrounding quotes
*
* A malformed leading character (e.g. a stray '=' with no name before it)
* is simply skipped over, one character at a time — the same outcome the
* old regex produced by failing to match at that position and retrying at
* the next one.
*/
const validAttrStrRegxp = new RegExp('(\\s*)([^\\s=]+)(\\s*=)?(\\s*([\'"])(([\\s\\S])*?)\\5)?', 'g');
function scanAttributeTokens(attrStr) {
const tokens = [];
const len = attrStr.length;
let i = 0;

while (i < len) {
const tokenStart = i;

// Leading whitespace before the name.
while (i < len && isWhiteSpace(attrStr[i])) i++;
if (i >= len) break; // trailing whitespace only — nothing left to read

if (attrStr[i] === '=') {
// No name before this '=' — not a valid attribute start. Move past
// just this one character and try again from the next position.
i = tokenStart + 1;
continue;
}

const leadingWs = attrStr.slice(tokenStart, i);

// Attribute name — everything up to the next whitespace or '='.
const nameStart = i;
while (i < len && !isWhiteSpace(attrStr[i]) && attrStr[i] !== '=') i++;
const name = attrStr.slice(nameStart, i);

// Optional whitespace + '='.
let equalsGroup; // whitespace + '=' text, or undefined if absent
let j = i;
while (j < len && isWhiteSpace(attrStr[j])) j++;
if (j < len && attrStr[j] === '=') {
equalsGroup = attrStr.slice(i, j + 1);
i = j + 1;
}

// Optional whitespace + quoted value.
let quoteChar;
let value;
let k = i;
while (k < len && isWhiteSpace(attrStr[k])) k++;
if (k < len && (attrStr[k] === '"' || attrStr[k] === "'")) {
const valueStart = k + 1;
const closeIdx = attrStr.indexOf(attrStr[k], valueStart);
if (closeIdx !== -1) {
quoteChar = attrStr[k];
value = attrStr.slice(valueStart, closeIdx);
i = closeIdx + 1;
}
// No closing quote found anywhere in the rest of the string — leave
// quoteChar/value undefined, same as the old regex's group failing
// to match a backreference-less run.
}

const token = { startIndex: tokenStart };
token[1] = leadingWs;
token[2] = name;
token[3] = equalsGroup;
token[4] = quoteChar !== undefined ? true : undefined;
token[5] = quoteChar;
token[6] = value;
tokens.push(token);
}

return tokens;
}

//attr, ="sd", a="amit's", a="sd"b="saf", ab cd=""

Expand All @@ -429,7 +522,7 @@ function validateAttributeString(attrStr, options) {

//if(attrStr.trim().length === 0) return true; //empty string

const matches = (0,_util_js__WEBPACK_IMPORTED_MODULE_0__/* .getAllMatches */ .Xe)(attrStr, validAttrStrRegxp);
const matches = scanAttributeTokens(attrStr);
const attrNames = {};

for (let i = 0; i < matches.length; i++) {
Expand Down Expand Up @@ -532,7 +625,6 @@ function getPositionFromMatch(match) {
return match.startIndex + match[1].length;
}


/***/ }),

/***/ 6009:
Expand Down
Loading
Loading