Spaces:
Sleeping
Sleeping
| // 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 | |
| }, | |
| }; |