diff --git a/docs/logic.js b/docs/logic.js index e7049e3b836..c0752162077 100644 --- a/docs/logic.js +++ b/docs/logic.js @@ -180,6 +180,19 @@ function scoreEntry(entry, matcher) { return 0; } +/** How many of an entry's links carry every search term. */ +function countLinkHits(entry, matchers) { + let hits = 0; + for (const url of entry.poc || []) { + const text = url.toLowerCase(); + if (matchers.every(m => wordStart(text, m.raw) >= 0 || (m.loose && m.loose.test(text)))) { + hits += 1; + if (hits >= 999) break; + } + } + return hits; +} + function runSearch(query) { const terms = query.match(/-?"[^"]+"|-?\S+/g) || []; const cleaned = terms.map(t => t.replace(/^(-?)"/, '$1').replace(/"$/, '')); @@ -200,9 +213,11 @@ function runSearch(query) { for (const entry of dataset) { let score = 0; let matched = true; + let describes = false; for (const matcher of matchers) { const termScore = scoreEntry(entry, matcher); if (termScore === 0) { matched = false; break; } + if (termScore >= 160) describes = true; score += termScore; } if (!matched) continue; @@ -210,15 +225,20 @@ function runSearch(query) { if (adjacent) { const at = wordStart(entry._descText, adjacent); - if (at >= 0) score += 300 + leadBonus(at); + if (at >= 0) { score += 300 + leadBonus(at); describes = true; } else if (wordStart(pocText(entry), adjacent) >= 0) score += 100; } entry._score = score; + // A term nobody's advisory text uses — "log4shell" — leaves every match tied + // at the weakest tier, where newest-first is meaningless. How many of the + // linked PoCs carry the term says far more than which CVE is newer. + entry._hits = describes ? 0 : countLinkHits(entry, matchers); results.push(entry); } results.sort((a, b) => { if (b._score !== a._score) return b._score - a._score; + if (b._hits !== a._hits) return b._hits - a._hits; if (b._year !== a._year) return b._year - a._year; return b._num - a._num; }); @@ -277,17 +297,29 @@ function pocRow(url) { `${escapeHTML(shortAge(hours))}`; } -/** Best-known repositories first, so the five shown by default are the five - * worth opening. Links without a star count keep their original order behind - * them; references from the CVE record have no repository to rank. */ +function compact(value) { + return value.toLowerCase().replace(/[^a-z0-9]/g, ''); +} + +/** Best links first. A repository named after the CVE is a proof of concept for + * it; a 5k-star scanner that merely mentions it is not, so stars only order + * repositories that are already about this CVE. Links with no star count keep + * their original order behind both — a reference has no repository to rank. */ function rankedLinks(entry) { if (entry._ranked) return entry._ranked; + const id = compact(entry.cve); const scored = (entry.poc || []).map((url, index) => { const parsed = repoFromUrl(url); const meta = parsed && repoMeta[(parsed.owner + '/' + parsed.repo).toLowerCase()]; - return { url, index, stars: meta ? meta[0] : -1 }; + return { + url, + index, + dedicated: compact(url).includes(id) ? 1 : 0, + stars: meta ? meta[0] : -1 + }; }); - scored.sort((a, b) => (b.stars - a.stars) || (a.index - b.index)); + scored.sort((a, b) => + (b.dedicated - a.dedicated) || (b.stars - a.stars) || (a.index - b.index)); entry._ranked = scored.map(item => item.url); return entry._ranked; } @@ -303,6 +335,9 @@ function resultRow(entry) { : (links.length > POC_PREVIEW ? `+ ${formatCount(links.length - POC_PREVIEW)} more repositories` : 'all repositories shown'); + const moreButton = links.length > 1 + ? `` + : ''; const dates = []; if (entry.published) dates.push(['published', entry.published]); @@ -324,12 +359,25 @@ function resultRow(entry) {