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 )