PageSourceSearch

https://powerline.ai/tools/js/views-dns.js?v=4fe7cb56f0

js powerline.ai collected 2026-10-05 14:06:29 UTC 15,331 bytes, 231 lines download raw bytes

1// Domain, record and blacklist tools.
2
3import { createResolver, parseDomainInput, parseIpv4 } from './dns.js';
4import { checkSpf, checkDmarc, checkDkim, checkBimi, checkMx, checkMtaSts, checkTlsRpt } from './records.js';
5import { checkIpBlacklists, checkDomainBlacklists, TIER_TEXT } from './blacklists.js';
6import { checkDomainHealth } from './domain-check.js';
7import { apiFetchers } from './remote.js';
8import { esc, pill, card, findingsList, recordBlock, tagsTable, statusMessage, scoreRing, readParams, writeParams, track, fmtNum } from './ui.js';
9
10const TOOL_PAGES = {
11  spf: '/tools/spf-checker',
12  dkim: '/tools/dkim-checker',
13  dmarc: '/tools/dmarc-checker',
14  bimi: '/tools/bimi-checker',
15  'mta-sts': '/tools/mta-sts-checker',
16  'tls-rpt': '/tools/tls-rpt-checker',
17  mx: '/tools/domain-check',
18  blacklists: '/tools/domain-blacklist-checker',
19};
20
21const fetchers = apiFetchers();
22
23function spfTree(node, depth = 0) {
24  if (!node) return '';
25  const kids = (node.terms || []).filter((t) => t.child);
26  const own = node.lookups ? ` <span class="cost">(${node.lookups} lookup${node.lookups === 1 ? '' : 's'})</span>` : '';
27  const errs = node.errors && node.errors.length && depth > 0 ? ` <span class="err">✕ ${esc(node.errors[0])}</span>` : '';
28  return `<li>${esc(node.domain)}${own}${errs}${kids.length ? `<ul>${kids.map((t) => spfTree(t.child, depth + 1)).join('')}</ul>` : ''}</li>`;
29}
30
31function extraFor(r) {
32  const x = r.extra || {};
33  let out = '';
34  if (r.id === 'spf' && x.tree) {
35    out += `<div><div class="section-label">DNS lookups: ${x.lookups} of 10</div><div class="bar"><span style="width:${Math.min(100, x.lookups * 10)}%;background:${x.lookups > 10 ? 'var(--fail)' : x.lookups >= 9 ? 'var(--warn)' : 'var(--pass)'}"></span></div></div>`;
36    if (x.tree.children.length) out += `<div><div class="section-label">Include tree</div><ul class="tree">${spfTree(x.tree)}</ul></div>`;
37  }
38  if (r.id === 'dkim' && x.selectors && x.selectors.length > 1) {
39    out += `<div class="table-wrap"><table class="data"><thead><tr><th>Selector</th><th>Key</th><th>Status</th><th>Managed by</th></tr></thead><tbody>${x.selectors.map((s) => `<tr><td class="mono">${esc(s.selector)}</td><td>${s.revoked ? 'revoked' : s.key && s.key.bits ? `${esc(s.key.type)} ${s.key.bits}-bit` : 'unreadable'}</td><td>${pill(s.status)}</td><td class="mono dim">${esc(s.cname || 'this domain')}</td></tr>`).join('')}</tbody></table></div>`;
40  }
41  if (r.id === 'mx' && x.hosts) {
42    out += `<div class="table-wrap"><table class="data"><thead><tr><th>Priority</th><th>Host</th><th>Provider</th><th>IPv4</th></tr></thead><tbody>${x.hosts.map((h) => `<tr><td class="num">${esc(h.priority)}</td><td class="mono">${esc(h.host)}</td><td>${esc(h.provider || '')}</td><td class="mono">${h.addresses.map((ip) => `<a href="/tools/ip-blacklist-checker?ip=${esc(ip)}">${esc(ip)}</a>`).join('<br>')}</td></tr>`).join('')}</tbody></table></div>`;
43  }
44  if (r.id === 'bimi') {
45    if (x.logoUrl && /^https:\/\//i.test(x.logoUrl)) {
46      // Loaded on request: fetching it tells the domain owner who looked.
47      out += `<div class="logo-preview" data-logo="${esc(x.logoUrl)}"><button type="button" class="linkish" data-show-logo>Show the logo</button><span class="tool-hint" style="margin:0">Loads it from the domain's own server, in the circle (Gmail, Yahoo) and rounded-square (Apple Mail) crops inboxes use.</span></div>`;
48    }
49    if (x.assets && x.assets.cert) {
50      const c = x.assets.cert;
51      out += `<dl class="kv"><dt>Certificate for</dt><dd>${esc(c.subject.O || c.subject.CN || '')}</dd><dt>Issued by</dt><dd>${esc(c.issuer.O || c.issuer.CN || '')}</dd><dt>Valid</dt><dd>${esc((c.notBefore || '').slice(0, 10))} to ${esc((c.notAfter || '').slice(0, 10))}</dd>${c.sans.length ? `<dt>Domains</dt><dd>${esc(c.sans.join(', '))}</dd>` : ''}</dl>`;
52    }
53  }
54  if (r.id === 'mta-sts' && x.policy) {
55    out += `<div><div class="section-label">Policy file (${x.policySource === 'pasted' ? 'pasted' : esc(x.policyUrl)})</div>${recordBlock([`version: ${x.policy.version || ''}`, `mode: ${x.policy.mode || ''}`, ...x.policy.mx.map((m) => `mx: ${m}`), `max_age: ${x.policy.maxAge ?? ''}`].join('\n'))}</div>`;
56  }
57  return out;
58}
59
60export function renderRecord(r, { collapsible = false, open = true, link = null } = {}) {
61  let body = '';
62  if (r.records && r.records.length > 1 && r.id !== 'dkim') body += `<div><div class="section-label">${r.records.length}
62 records found</div>${r.records.map(recordBlock).join('')}</div>`;
63  else if (r.record) body += recordBlock(r.record);
64  body += extraFor(r);
65  body += findingsList(r.findings);
66  body += tagsTable(r.tags);
67  return card({ title: r.title, status: r.status, headline: r.headline, sub: r.extra && r.extra.queried ? r.extra.queried : '', body, collapsible, open, link });
68}
69
70function blacklistTable(res) {
71  const order = ['major', 'standard', 'network'];
72  const rows = res.rows.slice().sort((a, b) => order.indexOf(a.tier) - order.indexOf(b.tier) || (a.state === 'listed' ? -1 : 0) - (b.state === 'listed' ? -1 : 0));
73  let tier = null;
74  let html = '';
75  for (const row of rows) {
76    if (row.tier !== tier) {
77      tier = row.tier;
78      html += `<tr><th colspan="3">${esc(TIER_TEXT[tier] || tier)}</th></tr>`;
79    }
80    const detail = row.state === 'listed'
81      ? `${row.reason ? esc(row.reason) : `Returned ${esc((row.codes || []).join(', '))}`}`
82      : row.state === 'unavailable' ? esc(row.note || '') : esc(row.about || '');
83    html += `<tr class="${row.state === 'listed' ? (row.tier === 'major' ? 'lvl-fail' : 'lvl-warn') : ''}"><td><a href="${esc(row.lookup)}" rel="noopener" target="_blank">${esc(row.name)}</a><div class="card-sub">${esc(row.zone)}</div></td><td>${pill(row.state === 'clean' ? 'clean' : row.state)}</td><td class="dim">${detail}</td></tr>`;
84  }
85  return `<div class="table-wrap"><table class="data"><thead><tr><th>List</th><th>Result</th><th>Detail</th></tr></thead><tbody>${html}</tbody></table></div>`;
86}
87
88export function renderBlacklist(res, title) {
89  const findings = [];
90  if (res.note) findings.push({ level: 'info', text: res.note });
91  const listed = res.rows.filter((r) => r.state === 'listed');
92  for (const l of listed) findings.push({ level: l.tier === 'major' ? 'fail' : 'warn', text: `Listed on ${l.name}. Follow its lookup link for the reason and removal steps; most lists delist automatically once the cause stops.` });
93  if (!listed.length && res.checked) findings.push({ level: 'pass', text: `Not listed on any of the ${res.checked} lists that answered.` });
94  if (res.unavailable) findings.push({ level: 'info', text: `${res.unavailable} list${res.unavailable === 1 ? '' : 's'} could not be queried from a public resolver and ${res.unavailable === 1 ? 'is' : 'are'} marked Unavailable rather than guessed. Check ${res.unavailable === 1 ? 'it' : 'them'} with the link.` });
95  return card({ title: title || res.target, status: res.status, headline: res.headline, sub: res.kind === 'ip' ? `IPv4 ${res.target}` : res.target, body: findingsList(findings) + (res.rows.length ? blacklistTable(res) : '') });
96}
97
98function domainCheckView(out) {
99  const counts = out.checks.reduce((m, c) => { m[c.status] = (m[c.status] || 0) + 1; return m; }, {});
100  const gradeText = { strong: 'Strong setup', fair: 'Room to improve', weak: 'Needs work', unknown: 'Incomplete' }[out.grade];
101  const tiles = out.checks.map((c) => `<a class="check-tile" href="#check-${esc(c.id)}"><div class="t">${esc(c.title)} ${pill(c.status)}</div><div class="h">${esc(c.headline)}</div></a>`).join('');
102  const fixes = out.fixes.length
103    ? `<section class="card"><div class="card-head"><span class="card-title">What to fix first</span></div><div class="card-body"><ol class="fix-list">${out.fixes.map((f) => `<li><div><strong>${esc(f.title)}.</strong> ${esc(f.text)}</div></li>`).join('')}</ol></div></section>`
104    : '';
105  const cards = out.checks.map((c) => {
106    const link = TOOL_PAGES[c.id] && c.id !== 'mx' ? { href: `${TOOL_PAGES[c.id]}?domain=${encodeURIComponent(out.domain)}`, text: 'Open checker →' } : null;
107    const html = c.id === 'blacklists'
108      ? card({ title: 'Blacklists', status: c.status, headline: c.headline, body: findingsList(c.findings) + [c.extra.domain, ...c.extra.ips].map((b) => `<div><div class="section-label">${esc(b.kind === 'ip' ? `Mail server ${b.target}` : `Domain ${b.target}`)} · ${esc(b.headline)}</div>
108${blacklistTable(b)}</div>`).join(''), collapsible: true, open: c.status === 'fail' || c.status === 'warn' })
109      : renderRecord(c, { collapsible: true, open: c.status === 'fail' || c.status === 'warn', link });
110    return `<div id="check-${esc(c.id)}">${html}</div>`;
111  }).join('');
112  return `<section class="card"><div class="score-card">${scoreRing(out.score)}<div class="score-meta"><h3>${esc(out.domain)}: ${esc(gradeText)}</h3><p>${counts.pass || 0} passing, ${(counts.warn || 0)} with warnings, ${counts.fail || 0} failing${counts.info ? `, ${counts.info} optional not set up` : ''}. ${out.partial ? 'Some checks could not run, so the score covers only the rest. ' : ''}Checked ${fmtNum(out.dnsQueries)} DNS records.</p></div></div><div class="check-grid">${tiles}</div></section>${fixes}${cards}`;
113}
114
115function crossLinks(domain, current) {
116  const items = [
117    ['domain-check', 'Full domain check', '/tools/domain-check'],
118    ['spf', 'SPF', '/tools/spf-checker'],
119    ['dkim', 'DKIM', '/tools/dkim-checker'],
120    ['dmarc', 'DMARC', '/tools/dmarc-checker'],
121    ['bimi', 'BIMI', '/tools/bimi-checker'],
122    ['mta-sts', 'MTA-STS', '/tools/mta-sts-checker'],
123    ['tls-rpt', 'TLS-RPT', '/tools/tls-rpt-checker'],
124    ['domain-blacklist', 'Blacklists', '/tools/domain-blacklist-checker'],
125  ].filter(([id]) => id !== current);
126  return `<p class="tool-hint" style="text-align:center;margin-top:1.5rem">Check ${esc(domain)} for: ${items.map(([, label, href]) => `<a href="${href}?domain=${encodeURIComponent(domain)}">${label}</a>`).join(' · ')}</p>`;
127}
128
129const RECORD_RUNNERS = {
130  spf: (dns, d) => checkSpf(dns, d),
131  dmarc: (dns, d) => checkDmarc(dns, d),
132  dkim: (dns, d, f) => checkDkim(dns, d, f.selector),
133  bimi: (dns, d, f) => checkBimi(dns, d, f.selector, fetchers),
134  'mta-sts': (dns, d, f) => checkMtaSts(dns, d, { ...fetchers, policyText: f.policy || null }),
135  'tls-rpt': (dns, d) => checkTlsRpt(dns, d),
136  mx: (dns, d) => checkMx(dns, d),
137};
138
139export function mount(tool, form, results, setError) {
140  const field = (n) => form.elements.namedItem(n);
141  const val = (n) => (field(n) ? field(n).value.trim() : '');
142  const button = form.querySelector('button[type=submit]');
143  let running = false;
144
145  async function run() {
146    if (running) return;
147    setError('');
148    const dns = createResolver();
149    const started = performance.now();
150    try {
151      running = true;
152      button.disabled = true;
153      if (tool === 'ip-blacklist') {
154        const ip = val('ip');
155        if (!parseIpv4(ip)) { setError(ip.includes(':') ? 'IPv6 is not supported: almost no blacklist publishes IPv6 data.' : 'Enter a valid IPv4 address, like 203.0.113.10.'); return; }
156        writeParams({ ip });
157        results.innerHTML = statusMessage(`Checking ${ip} against IP blacklists…`);
158        const res = await checkIpBlacklists(dns, ip);
159        results.innerHTML = renderBlacklist(res, 'IP blacklists') + (res.note && !res.rows.length ? `<p class="tool-hint">${esc(res.note)}</p>` : '');
160        track('tool_run', { tool, status: res.status });
161        return;
162      }
163      const parsed = parseDomainInput(val('domain'));
164      if (parsed.error) { setError(parsed.error); return; }
165      const domain = parsed.domain;
166      if (field('domain')) field('domain').value = domain;
167      const extra = { selector: val('selector'), policy: val('policy') };
168      writeParams({ domain, selector: extra.selector });
169      if (tool === 'domain-check') {
170        results.innerHTML = statusMessage(`Checking ${domain}: SPF, DKIM, DMARC, MX, MTA-STS, TLS-RPT, BIMI and blacklists…`);
171        const out = await checkDomainHealth(dns, domain, { ...fetchers, dkimSelector: extra.selector });
172        results.innerHTML = domainCheckView(out) + crossLinks(domain, 'domain-check');
173        track('tool_run', { tool, status: out.grade, score: out.score });
174        return;
175      }
176      if (tool === 'domain-blacklist') {
177        results.innerHTML = statusMessage(`Checking ${domain} against domain blacklists…`);
178        const res = await checkDomainBlacklists(dns, domain);
179        results.innerHTML = renderBlacklist(res, 'Domain blacklists') + crossLinks(domain, 'domain-blacklist');
180        track('tool_run', { tool, status: res.status });
181        return;
182      }
183      results.innerHTML = statusMessage(`Looking up ${domain}…`);
184      const r = await RECORD_RUNNERS[tool](dns, domain, extra);
185      let html = renderRecord(r);
186      if (tool === 'mta-sts' && r.record && !(r.extra && r.extra.policy)) {
187        html += `<section class="card"><div class="card-head"><span class="card-title">Check a policy file by hand</span></div><div class="card-body"><p class="tool-hint" style="margin:0">Paste the contents of <code>${esc(r.extra.policyUrl)}</code> (or a draft you are about to publish) to check it against this domain's MX hosts.</p><textarea class="tool-textarea" id="policy-paste" spellcheck="false" placeholder="version: STSv1&#10;mode: enforce&#10;mx: mx1.example.com&#10;max_age: 604800"></textarea><div class="tool-actions"><button type="button" class="run-btn" id="policy-check">Check pasted policy</button></div></div></section>`;
188      }
189      results.innerHTML = html + crossLinks(domain, tool);
190      const pb = results.querySelector('#policy-check');
191      if (pb) pb.addEventListener('click', () => {
192        const text = results.querySelector('#policy-paste').value;
193        if (!text.trim()) return;
194        if (!field('policy')) { const h = document.createElement('input'); h.type = 'hidden'; h.name = 'policy'; form.appendChild(h); }
195        field('policy').value = text;
196        run();
197      });
198      track('tool_run', { tool, status: r.status });
199    } catch (e) {
200      results.innerHTML = '';
201      setError(`Something went wrong: ${e.message}`);
202    } finally {
203      running = false;
204      button.disabled = false;
205      if (field('policy') && field('policy').type === 'hidden') field('policy').value = '';
206      results.dataset.ms = String(Math.round(performance.now() - started));
207    }
208  }
209
210  form.addEventListener('submit', (e) => { e.preventDefault(); run(); });
211  results.addEventListener('click', (e) => {
212    const b = e.target.closest('[data-show-logo]');
213    if (!b) return;
214    const box = b.closest('[data-logo]');
215    const src = box.getAttribute('data-logo');
216    box.textContent = '';
217    for (const round of [true, false]) {
218      const img = document.createElement('img');
219      img.src = src;
220      img.alt = round ? 'BIMI logo, round crop' : 'BIMI logo, square crop';
221      img.referrerPolicy = 'no-referrer';
222      if (round) img.className = 'round';
223      box.appendChild(img);
224    }
225  });
226  const p = readParams();
227  let auto = false;
228  for (const k of ['domain', 'selector', 'ip']) if (p[k] && field(k)) { field(k).value = p[k]; if (k !== 'selector') auto = true; }
229  if (p.email && field('domain')) { field('domain').value = p.email; auto = true; }
230  if (auto) run();
231}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.