b7addb5c7a
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
40 lines
1.1 KiB
TypeScript
40 lines
1.1 KiB
TypeScript
import { readFile } from 'node:fs/promises'
|
|
import { resolve, extname } from 'node:path'
|
|
import { existsSync } from 'node:fs'
|
|
|
|
const MIME: Record<string, string> = {
|
|
'.jpg': 'image/jpeg',
|
|
'.jpeg': 'image/jpeg',
|
|
'.png': 'image/png',
|
|
'.gif': 'image/gif',
|
|
'.webp': 'image/webp',
|
|
'.avif': 'image/avif',
|
|
'.svg': 'image/svg+xml',
|
|
}
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
const url = getRequestURL(event)
|
|
const pathMatch = url.pathname.match(/^\/posts\/([a-z0-9-]+)\/img\/(.+)$/)
|
|
if (!pathMatch) return
|
|
|
|
const [, slug, filename] = pathMatch
|
|
|
|
// 防止路径遍历攻击
|
|
if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) {
|
|
throw createError({ statusCode: 400 })
|
|
}
|
|
|
|
const ext = extname(filename).toLowerCase()
|
|
if (!MIME[ext]) return
|
|
|
|
const filePath = resolve('content/posts', slug, 'img', filename)
|
|
if (!existsSync(filePath)) {
|
|
throw createError({ statusCode: 404 })
|
|
}
|
|
|
|
const data = await readFile(filePath)
|
|
setResponseHeader(event, 'content-type', MIME[ext])
|
|
setResponseHeader(event, 'cache-control', 'public, max-age=31536000')
|
|
return data
|
|
})
|