← back to Dw Domain Fleet

output/leak-reverify-checker.js

121 lines

#!/usr/bin/env node
/* READ-ONLY vendor-href leak re-verify. Fetches each site's homepage + one PDP
 * from the LIVE local port, extracts every URL-path / data-handle / data-sku /
 * href, and tests each against the PRODUCTION _VENDOR_SCRUB regex (the same
 * definition of "a vendor leak" the render layer uses). /buy/<slug> and
 * /product/<clean-slug> are expected to NOT match. Reports per-site leak count.
 * No writes, no edits, no deploys. */
const http = require('http');

// ---- build the production vendor regex from the deployed render.js denylist ----
const renderSrc = require('fs').readFileSync('/root/public-projects/dw-domain-fleet/shared/render.js', 'utf8');
// pull VENDORS_DENYLIST array literal
const m = renderSrc.match(/const VENDORS_DENYLIST = (\[[\s\S]*?\]);/);
if (!m) { console.error('could not parse VENDORS_DENYLIST'); process.exit(2); }
const VENDORS_DENYLIST = eval(m[1]);
const _VENDOR_SCRUB = VENDORS_DENYLIST
  .concat(['brunschwig fils', 'lee jofa modern', 'gp j baker', 'g p j baker',
           'cole and son', 'cole son', 'clarke and clarke', 'clarke clarke',
           'morris and co', 'morris co', 'caroline cecil textiles', 'caroline cecil'])
  .slice().sort((a, b) => b.length - a.length)
  .map(v => new RegExp('\\b' + v.split(/[^a-z0-9]+/i).filter(Boolean).join('[^a-z0-9]*') + '\\b', 'ig'));

function vendorHit(s) {
  if (!s) return null;
  for (const re of _VENDOR_SCRUB) { re.lastIndex = 0; const r = re.exec(s); if (r) return r[0]; }
  return null;
}

function get(port, path) {
  return new Promise((res) => {
    const req = http.get({ host: '127.0.0.1', port, path, timeout: 12000,
      headers: { 'User-Agent': 'leak-reverify' } }, (r) => {
      let d = ''; r.on('data', c => d += c); r.on('end', () => res({ status: r.statusCode, body: d, headers: r.headers }));
    });
    req.on('timeout', () => { req.destroy(); res({ status: 0, body: '', err: 'timeout' }); });
    req.on('error', (e) => res({ status: 0, body: '', err: e.message }));
  });
}

// Extract candidate leak-bearing strings from rendered HTML.
function extractTokens(html) {
  const toks = [];
  const push = (kind, val) => { if (val) toks.push({ kind, val }); };
  let r;
  // raw main-store product URLs
  const reDW = /designerwallcoverings\.com\/products\/[^"'\s)>]+/ig;
  while ((r = reDW.exec(html))) push('dw-store-url', r[0]);
  // attribute values
  for (const attr of ['href', 'data-handle', 'data-sku', 'data-title', 'content']) {
    const re = new RegExp(attr + '="([^"]*)"', 'ig');
    while ((r = re.exec(html))) push(attr, r[1]);
  }
  // bare path tokens
  for (const p of ['/product/', '/p/', '/buy/']) {
    const re = new RegExp(p.replace(/\//g, '\\/') + '[^"\'\\s)>]+', 'ig');
    while ((r = re.exec(html))) push('path' + p, r[0]);
  }
  return toks;
}

function firstProductPath(html) {
  // find a real per-SKU link to fetch as the PDP sample
  let r;
  const re = /href="(\/product\/[^"#]+)"/ig;
  const seen = [];
  while ((r = re.exec(html)) && seen.length < 3) seen.push(r[1]);
  return seen[0] || null;
}

async function scanSite(name, port) {
  const out = { name, port, status: {}, leaks: [], pdpPath: null, fetched: [] };
  const home = await get(port, '/');
  out.status.home = home.status + (home.err ? '(' + home.err + ')' : '');
  if (home.status !== 200) return out;
  out.fetched.push('/');
  scanHtml(home.body, '/', out);

  let pdp = firstProductPath(home.body);
  out.pdpPath = pdp;
  if (pdp) {
    const pr = await get(port, pdp);
    out.status.pdp = pr.status + (pr.err ? '(' + pr.err + ')' : '');
    if (pr.status === 200) { out.fetched.push(pdp); scanHtml(pr.body, pdp, out); }
  } else {
    out.status.pdp = 'no-product-link';
  }
  return out;
}

function scanHtml(html, page, out) {
  for (const { kind, val } of extractTokens(html)) {
    // /buy/ and /product/ slugs are scrubbed-clean by design; still test them —
    // a hit there IS a leak. We only skip nothing; the regex decides.
    const hit = vendorHit(val);
    if (hit) out.leaks.push({ page, kind, token: hit, sample: val.slice(0, 160) });
  }
}

(async () => {
  const SITES = [];
  const FLEET = [
    '1800wallcoverings','asseeninhotels','asseeninla','asseeninmovies','asseeninshowrooms',
    'barwallpaper','blankstocklining','bleachfriendly','carmelwallpaper','cfafabrics',
    'classawallcovering','commercialsalesreps','commercialwallcovering','designermagnetics',
    'etciemurals','fabricfridays','fireratedwallcovering','flocked','grassclothwallcovering',
    'handmadewallcovering','hollywoodwallcovering','hospitalitysalesagency','malibuwallpaper',
    'montereywallpaper','naturalwalltextures','patterndesignlab','philliperomano','printmurals',
    'restaurantfabrics','restaurantmurals','roomsettings','sheltermagazines','specifywallpaper',
    'stevenabramsphotography','thehotelwallpaper','traditionalwhimsy','unitedstateswallpaper',
    'wallcovering','wallpaperexports','wallpaperfromthe80s','wallpaperny','wallpaperpurchasing',
    'wallpaperweekly','wc01wallcoverings'];
  FLEET.forEach((n, i) => SITES.push({ name: 'dwf-' + n, port: 10001 + i }));
  SITES.push({ name: 'STANDALONE customdigitalmurals', port: 9896 });
  SITES.push({ name: 'STANDALONE carmelwallpapers', port: 9861 });
  SITES.push({ name: 'STANDALONE naturaltextilewallpaper', port: 9897 });

  const results = [];
  for (const s of SITES) results.push(await scanSite(s.name, s.port));
  console.log(JSON.stringify(results, null, 2));
})();