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>
`;
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);
let p = parsed.pathname;
if (parsed.search) {
let safe = parsed.search.slice(1).replace(/[^a-zA-Z0-9._-]/g, '_');
// Hash overly long query strings to avoid ENAMETOOLONG
if (safe.length > 80) {
safe = crypto.createHash('md5').update(parsed.search).digest('hex').slice(0, 12);
}
p += '_q_' + safe;
}
// also hash if path itself is way too long
const basename = path.basename(p);
if (basename.length > 200) {
// Append correct extension if missing — so express.static sends proper Content-Type
const baseName = path.basename(p);
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 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(/^\/+/, '');
}
@@ -99,7 +123,8 @@ async function main() {
if (!rurl.startsWith('http')) return;
try {
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 */ }
});
@@ -115,10 +140,10 @@ async function main() {
// Write resources
fs.mkdirSync(MIRROR, { recursive: true });
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);
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);
fs.mkdirSync(path.dirname(local), { recursive: true });
fs.writeFileSync(local, buf);