// pages/api/jekyll/static/[...path].js // Serve static Jekyll files import path from 'path'; import fs from 'fs-extra'; import { lookup } from 'mime-types'; export default async function handler(req, res) { try { const { path: pathArray } = req.query; if (!pathArray || !Array.isArray(pathArray) || pathArray.length === 0) { return res.status(400).json({ error: 'Invalid path' }); } const [siteName, ...filePath] = pathArray; const requestedFile = filePath.join('/') || 'index.html'; // Security: prevent path traversal if (siteName.includes('..') || requestedFile.includes('..')) { return res.status(403).json({ error: 'Access denied' }); } const sitePath = path.join('/app/projects', siteName, '_site'); const fullPath = path.join(sitePath, requestedFile); // Check if site exists if (!(await fs.pathExists(sitePath))) { return res.status(404).json({ error: 'Site not found' }); } // Try to find the file let targetFile = fullPath; // If requesting a directory, try index.html if ((await fs.pathExists(fullPath)) && (await fs.stat(fullPath)).isDirectory()) { targetFile = path.join(fullPath, 'index.html'); } // If file doesn't exist, try adding .html extension if (!(await fs.pathExists(targetFile))) { targetFile = fullPath + '.html'; } // Final check if (!(await fs.pathExists(targetFile))) { return res.status(404).json({ error: 'File not found' }); } // Security: ensure file is within site directory const resolvedPath = path.resolve(targetFile); const resolvedSitePath = path.resolve(sitePath); if (!resolvedPath.startsWith(resolvedSitePath)) { return res.status(403).json({ error: 'Access denied' }); } // Get file info const stats = await fs.stat(targetFile); const mimeType = lookup(targetFile) || 'application/octet-stream'; // Set headers res.setHeader('Content-Type', mimeType); res.setHeader('Content-Length', stats.size); res.setHeader('Last-Modified', stats.mtime.toUTCString()); res.setHeader('Cache-Control', 'public, max-age=3600'); // Handle conditional requests const ifModifiedSince = req.headers['if-modified-since']; if (ifModifiedSince && new Date(ifModifiedSince) >= stats.mtime) { return res.status(304).end(); } // Stream the file const fileStream = fs.createReadStream(targetFile); fileStream.pipe(res); fileStream.on('error', (error) => { console.error('Error streaming file:', error); if (!res.headersSent) { res.status(500).json({ error: 'Error reading file' }); } }); } catch (error) { console.error('Error serving static file:', error); if (!res.headersSent) { res.status(500).json({ error: 'Internal server error', details: error.message }); } } } export const config = { api: { responseLimit: '10mb', // Allow larger files }, };