← 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));
})();