From cf7d3d5618b8269c973cca2ed5439354cdceec26 Mon Sep 17 00:00:00 2001 From: tech Date: Wed, 4 Mar 2026 02:01:38 +0800 Subject: [PATCH] fix: use streaming and add path validation - Replace fs.readFileSync() with fs.createReadStream() to prevent memory exhaustion - Export validatePath() from fs-utils.ts - Use validatePath() to validate full path including project and version parameters - Fix incomplete path traversal protection - Add filename encoding in Content-Disposition header Co-Authored-By: Claude Sonnet 4.5 --- app/api/download/route.ts | 26 ++++++++++---------------- lib/fs-utils.ts | 2 +- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/app/api/download/route.ts b/app/api/download/route.ts index 95d8a37..9d7118c 100644 --- a/app/api/download/route.ts +++ b/app/api/download/route.ts @@ -1,8 +1,8 @@ // app/api/download/route.ts import { NextResponse } from 'next/server'; -import path from 'path'; import fs from 'fs'; import { RESOURCE_PATH } from '@/lib/constants'; +import { validatePath } from '@/lib/fs-utils'; export async function GET(request: Request) { const { searchParams } = new URL(request.url); @@ -17,18 +17,11 @@ export async function GET(request: Request) { ); } - // 安全检查:确保 filename 不包含路径遍历字符 - if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) { - return NextResponse.json( - { error: 'Invalid filename' }, - { status: 400 } - ); - } - - const filePath = path.join(RESOURCE_PATH, project, version, 'apks', filename); + // Validate the full path to prevent path traversal attacks + const filePath = validatePath(RESOURCE_PATH, project, version, 'apks', filename); try { - // 检查文件是否存在 + // Check if file exists if (!fs.existsSync(filePath)) { return NextResponse.json( { error: 'File not found' }, @@ -36,14 +29,15 @@ export async function GET(request: Request) { ); } - // 读取文件并流式返回 - const file = fs.readFileSync(filePath); + // Use streaming to avoid loading entire file into memory + const fileStream = fs.createReadStream(filePath); + const stat = await fs.promises.stat(filePath); - return new NextResponse(file, { + return new NextResponse(fileStream as any, { headers: { 'Content-Type': 'application/vnd.android.package-archive', - 'Content-Disposition': `attachment; filename="${filename}"`, - 'Content-Length': file.length.toString(), + 'Content-Disposition': `attachment; filename="${encodeURIComponent(filename)}"`, + 'Content-Length': stat.size.toString(), }, }); } catch (error) { diff --git a/lib/fs-utils.ts b/lib/fs-utils.ts index 2762672..d2c3032 100644 --- a/lib/fs-utils.ts +++ b/lib/fs-utils.ts @@ -6,7 +6,7 @@ import { RESOURCE_PATH } from './constants'; /** * Validate that a path doesn't escape the base directory (prevents path traversal) */ -function validatePath(basePath: string, ...segments: string[]): string { +export function validatePath(basePath: string, ...segments: string[]): string { const fullPath = path.resolve(basePath, ...segments); const normalizedBase = path.resolve(basePath); if (!fullPath.startsWith(normalizedBase)) {