운영 업로드 파일 제공 경로 보강
This commit is contained in:
84
server/routes/uploads/[...path].get.js
Normal file
84
server/routes/uploads/[...path].get.js
Normal file
@@ -0,0 +1,84 @@
|
||||
import { createReadStream } from 'node:fs'
|
||||
import { stat } from 'node:fs/promises'
|
||||
import { extname, join, relative } from 'node:path'
|
||||
import { createError, getRequestURL, sendStream, setResponseHeader } from 'h3'
|
||||
|
||||
const uploadRoot = join(process.cwd(), 'public', 'uploads')
|
||||
const contentTypes = new Map([
|
||||
['.jpg', 'image/jpeg'],
|
||||
['.jpeg', 'image/jpeg'],
|
||||
['.png', 'image/png'],
|
||||
['.webp', 'image/webp'],
|
||||
['.gif', 'image/gif'],
|
||||
['.svg', 'image/svg+xml'],
|
||||
['.ico', 'image/x-icon']
|
||||
])
|
||||
|
||||
/**
|
||||
* 업로드 요청 URL을 디스크 파일 경로로 변환한다.
|
||||
* @param {string} pathname - 요청 경로
|
||||
* @returns {string} 디스크 파일 경로
|
||||
*/
|
||||
const resolveUploadFilePath = (pathname) => {
|
||||
let decodedPath = ''
|
||||
|
||||
try {
|
||||
decodedPath = decodeURIComponent(pathname)
|
||||
} catch {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: '업로드 파일 경로가 올바르지 않습니다.'
|
||||
})
|
||||
}
|
||||
|
||||
const relativeUrlPath = decodedPath.replace(/^\/uploads\/?/g, '')
|
||||
|
||||
if (!relativeUrlPath || relativeUrlPath.includes('\0')) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
message: '업로드 파일을 찾을 수 없습니다.'
|
||||
})
|
||||
}
|
||||
|
||||
const filePath = join(uploadRoot, relativeUrlPath)
|
||||
const relativeDiskPath = relative(uploadRoot, filePath)
|
||||
|
||||
if (relativeDiskPath.startsWith('..') || relativeDiskPath === '') {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: '업로드 파일 경로가 올바르지 않습니다.'
|
||||
})
|
||||
}
|
||||
|
||||
return filePath
|
||||
}
|
||||
|
||||
/**
|
||||
* 런타임 업로드 파일 제공 API
|
||||
* @param {import('h3').H3Event} event - 요청 이벤트
|
||||
* @returns {Promise<void>} 업로드 파일 스트림
|
||||
*/
|
||||
export default defineEventHandler(async (event) => {
|
||||
const filePath = resolveUploadFilePath(getRequestURL(event).pathname)
|
||||
const fileStat = await stat(filePath).catch(() => null)
|
||||
|
||||
if (!fileStat?.isFile()) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
message: '업로드 파일을 찾을 수 없습니다.'
|
||||
})
|
||||
}
|
||||
|
||||
const extension = extname(filePath).toLowerCase()
|
||||
const contentType = contentTypes.get(extension)
|
||||
|
||||
if (contentType) {
|
||||
setResponseHeader(event, 'content-type', contentType)
|
||||
}
|
||||
|
||||
setResponseHeader(event, 'content-length', String(fileStat.size))
|
||||
setResponseHeader(event, 'cache-control', 'no-cache')
|
||||
setResponseHeader(event, 'last-modified', fileStat.mtime.toUTCString())
|
||||
|
||||
return sendStream(event, createReadStream(filePath))
|
||||
})
|
||||
Reference in New Issue
Block a user