From bcdecee1a9fe23ab852ffe1fcbe17e72ae7d5f3f Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Sat, 16 May 2026 16:28:35 -0300 Subject: [PATCH] ci: harden typhon telemetry publisher quoting --- Jenkinsfile | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 9c3a50f..88d7eb7 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -107,8 +107,12 @@ function unescapeXml(value) { } function attr(attrs, name) { - const match = attrs.match(new RegExp(`(?:^|\\s)${name}="([^"]*)"`)); - return match ? unescapeXml(match[1]) : ''; + const needle = `${name}="`; + const start = attrs.indexOf(needle); + if (start < 0) return ''; + const valueStart = start + needle.length; + const valueEnd = attrs.indexOf('"', valueStart); + return valueEnd < 0 ? '' : unescapeXml(attrs.slice(valueStart, valueEnd)); } function collectSourceFiles(rootDir) { @@ -134,7 +138,7 @@ function collectSourceFiles(rootDir) { } function categoryForClassname(classname) { - const normalized = String(classname || '').split('\\').join('/'); + const normalized = String(classname || '').split(String.fromCharCode(92)).join('/'); const relative = normalized.includes('/tests/') ? normalized.slice(normalized.indexOf('/tests/') + '/tests/'.length) : (normalized.startsWith('tests/') ? normalized.slice('tests/'.length) : normalized); @@ -147,7 +151,7 @@ function categoryForClassname(classname) { function parseTestCases(junit) { const cases = []; - const re = new RegExp(']*)>([\\s\\S]*?)|]*)/>', 'g'); + const re = new RegExp(']*)>(.*?)|]*)/>', 'gs'); let match; while ((match = re.exec(junit)) !== null) { const attrs = match[1] || match[3] || ''; @@ -174,7 +178,7 @@ const cov = fs.existsSync(coveragePath) ? JSON.parse(fs.readFileSync(coveragePat const total = cov.total || {}; const sourceFiles = sourceRoots.flatMap((root) => collectSourceFiles(root)); const overLimitFiles = sourceFiles - .map((file) => ({ file, lines: fs.readFileSync(file, 'utf8').split(new RegExp('\\r?\\n')).length })) + .map((file) => ({ file, lines: fs.readFileSync(file, 'utf8').split(String.fromCharCode(10)).length })) .filter((item) => item.lines > 500); const report = { @@ -310,9 +314,9 @@ const sourceLinesOver500 = Number(quality.hygiene?.sourceLinesOver500 ?? 0); function esc(value) { return String(value ?? '') - .split('\\').join('\\\\') - .split('"').join('\\"') - .split('\n').join('\\n'); + .split(String.fromCharCode(92)).join(String.fromCharCode(92, 92)) + .split('"').join(String.fromCharCode(92) + '"') + .split(String.fromCharCode(10)).join(String.fromCharCode(92) + 'n'); } function labelString(labels) { @@ -322,10 +326,16 @@ function labelString(labels) { function fetchCounter(targetStatus) { try { const metrics = execSync(`curl -fsS ${gateway}/metrics`, { encoding: 'utf8' }); - const re = new RegExp(`platform_quality_gate_runs_total\\{[^}]*suite=\\"${suite}\\"[^}]*status=\\"${targetStatus}\\"[^}]*\\}\\s+(\\d+(?:\\.\\d+)?)`); - const match = metrics.match(re); - if (!match) return 0; - return Number(match[1]); + for (const line of metrics.split(String.fromCharCode(10))) { + if ( + line.startsWith('platform_quality_gate_runs_total{') && + line.includes(`suite="${suite}"`) && + line.includes(`status="${targetStatus}"`) + ) { + return Number(line.trim().split(' ').filter(Boolean).pop() || 0); + } + } + return 0; } catch { return 0; } @@ -392,7 +402,7 @@ const lines = [ try { execSync(`curl -fsS --data-binary @- ${gateway}/metrics/job/platform-quality-ci/suite/${suite}`, { - input: lines.join('\\n'), + input: lines.join(String.fromCharCode(10)), stdio: ['pipe', 'inherit', 'inherit'] }); } catch (error) {