fix: append correct file extension based on content-type (CSS/JS need them)

This commit is contained in:
aedes
2026-04-22 02:40:20 +03:00
parent c5adb0c108
commit 4a5c9c2d80

View File

@@ -42,23 +42,47 @@ const CHAT_OVERLAY = `
</script> </script>
`; `;
function urlToSafePath(u) { function mimeExt(ct) {
ct = (ct || '').split(';')[0].trim().toLowerCase();
if (ct.includes('css')) return '.css';
if (ct.includes('javascript') || ct.includes('ecmascript')) return '.js';
if (ct === 'image/png') return '.png';
if (ct === 'image/jpeg' || ct === 'image/jpg') return '.jpg';
if (ct === 'image/gif') return '.gif';
if (ct === 'image/svg+xml') return '.svg';
if (ct === 'image/webp') return '.webp';
if (ct === 'image/x-icon' || ct === 'image/vnd.microsoft.icon') return '.ico';
if (ct === 'font/woff2' || ct === 'application/font-woff2') return '.woff2';
if (ct === 'font/woff' || ct === 'application/font-woff') return '.woff';
if (ct === 'font/ttf' || ct === 'application/x-font-ttf') return '.ttf';
if (ct === 'font/otf') return '.otf';
if (ct.startsWith('text/html')) return '.html';
if (ct.startsWith('application/json')) return '.json';
return '';
}
function urlToSafePath(u, contentType) {
const parsed = new URL(u); const parsed = new URL(u);
let p = parsed.pathname; let p = parsed.pathname;
if (parsed.search) { if (parsed.search) {
let safe = parsed.search.slice(1).replace(/[^a-zA-Z0-9._-]/g, '_'); let safe = parsed.search.slice(1).replace(/[^a-zA-Z0-9._-]/g, '_');
// Hash overly long query strings to avoid ENAMETOOLONG
if (safe.length > 80) { if (safe.length > 80) {
safe = crypto.createHash('md5').update(parsed.search).digest('hex').slice(0, 12); safe = crypto.createHash('md5').update(parsed.search).digest('hex').slice(0, 12);
} }
p += '_q_' + safe; p += '_q_' + safe;
} }
// also hash if path itself is way too long // Append correct extension if missing — so express.static sends proper Content-Type
const basename = path.basename(p); const baseName = path.basename(p);
if (basename.length > 200) { if (!/\.[a-z0-9]{2,5}$/i.test(baseName) && contentType) {
const ext = mimeExt(contentType);
if (ext) p += ext;
}
// Hash if too long
const baseName2 = path.basename(p);
if (baseName2.length > 200) {
const ext = path.extname(p); const ext = path.extname(p);
const dir = path.dirname(p); const dir = path.dirname(p);
p = dir + '/' + crypto.createHash('md5').update(basename).digest('hex').slice(0, 16) + ext; p = dir + '/' + crypto.createHash('md5').update(baseName2).digest('hex').slice(0, 16) + ext;
} }
return p.replace(/^\/+/, ''); return p.replace(/^\/+/, '');
} }
@@ -99,7 +123,8 @@ async function main() {
if (!rurl.startsWith('http')) return; if (!rurl.startsWith('http')) return;
try { try {
const buf = await resp.body(); const buf = await resp.body();
if (buf && buf.length > 0) resources.set(rurl, buf); const ct = resp.headers()['content-type'] || '';
if (buf && buf.length > 0) resources.set(rurl, { buf, ct });
} catch (e) { /* redirect/204/etc */ } } catch (e) { /* redirect/204/etc */ }
}); });
@@ -115,10 +140,10 @@ async function main() {
// Write resources // Write resources
fs.mkdirSync(MIRROR, { recursive: true }); fs.mkdirSync(MIRROR, { recursive: true });
const mapping = new Map(); // absUrl → /q/mirror/<safepath> const mapping = new Map(); // absUrl → /q/mirror/<safepath>
for (const [rurl, buf] of resources) { for (const [rurl, { buf, ct }] of resources) {
const u = new URL(rurl); const u = new URL(rurl);
if (u.hostname !== HOST) continue; // skip third-party (cdnjs etc) — let browser load them if (u.hostname !== HOST) continue; // skip third-party (cdnjs etc) — let browser load them
const safe = urlToSafePath(rurl); const safe = urlToSafePath(rurl, ct);
const local = path.join(MIRROR, safe); const local = path.join(MIRROR, safe);
fs.mkdirSync(path.dirname(local), { recursive: true }); fs.mkdirSync(path.dirname(local), { recursive: true });
fs.writeFileSync(local, buf); fs.writeFileSync(local, buf);