CPAN-InGit
view release on metacpan or search on metacpan
share/public/cpan_cves.js view on Meta::CPAN
function compileRange(range) {
if (range === '*') return () => true;
if (typeof range !== 'string' || !range.trim()) throw new Error('Invalid affected-version range');
const conditions = range.split(',').map(condition => {
const match = condition.trim().match(/^(<=|>=|==|!=|=|<|>)\s*(\S+)$/);
if (!match) throw new Error(`Unsupported affected-version range: ${range}`);
return {operator: match[1], version: parseVersion(match[2])};
});
return version => conditions.every(condition => {
const order = compareVersions(version, condition.version);
switch (condition.operator) {
case '<': return order < 0;
case '<=': return order <= 0;
case '>': return order > 0;
case '>=': return order >= 0;
case '!=': return order !== 0;
default: return order === 0;
}
});
}
function parseFeed(text) {
// Some feed revisions prefix their JSON with a plaintext warning.
const start = text.indexOf('{');
if (start < 0) throw new Error('Missing CPANSA JSON');
const data = JSON.parse(text.slice(start));
if (!data || Array.isArray(data) || typeof data !== 'object') throw new Error('Invalid CPANSA database');
const index = new Map();
for (const [distribution, entries] of Object.entries(data)) {
if (!Array.isArray(entries)) throw new Error('Invalid CPANSA advisory list');
const advisories = entries.map(entry => {
if (!entry || typeof entry !== 'object') throw new Error('Invalid CPANSA advisory');
const ranges = entry.affected_versions ?? entry.version_range ?? [];
if (!Array.isArray(ranges)) throw new Error('Invalid affected-version list');
const alternatives = ranges.map(compileRange);
const ids = Array.isArray(entry.cves) ? entry.cves.filter(id => typeof id === 'string') : [];
if (typeof entry.cve_id === 'string') ids.push(entry.cve_id);
if (typeof entry.cpansa_id === 'string') ids.push(entry.cpansa_id);
return {ids: [...new Set(ids)], affects: version =>
alternatives.length === 0 || alternatives.some(matches => matches(version))};
});
index.set(distribution, advisories);
}
return index;
}
function findAdvisories(index, row) {
// Advisories apply to distribution releases, not a bundled module's $VERSION.
const filename = row.path.split('/').pop();
const release = filename.match(/^(.+)-(v?\d[\d._]*)(?:-TRIAL)?\.(?:tar\.(?:gz|bz2|xz|zst|Z)|tgz|tbz2|zip|tar)$/i);
if (!release) return [];
const advisories = index.get(release[1]);
if (!advisories) return [];
let version;
try { version = parseVersion(release[2]); } catch { return []; }
return advisories.filter(advisory => advisory.affects(version));
}
async function download(onStatus) {
onStatus('Downloading CVE listâ¦');
const response = await fetch(feedUrl, {credentials: 'omit', mode: 'cors'});
if (!response.ok) throw new Error(`CVE download: HTTP ${response.status}`);
const text = await response.text();
onStatus('Reading CVE listâ¦');
return parseFeed(text);
}
return Object.freeze({feedUrl, parseVersion, compareVersions, parseFeed, findAdvisories, download});
})();
( run in 1.170 second using v1.01-cache-2.11-cpan-3f962061493 )