From 4fdd6be18803c418ff69df40e36aa5267ad9bea9 Mon Sep 17 00:00:00 2001 From: MarSeventh <1193267292@qq.com> Date: Thu, 23 Jul 2026 11:43:33 +0800 Subject: [PATCH] fix: harden batch delete request handling --- functions/api/manage/delete/batch.js | 16 ++++++++++++---- functions/utils/deleteBatch.js | 26 ++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/functions/api/manage/delete/batch.js b/functions/api/manage/delete/batch.js index 38573fe7..9cf14cb6 100644 --- a/functions/api/manage/delete/batch.js +++ b/functions/api/manage/delete/batch.js @@ -12,7 +12,11 @@ const corsHeaders = { 'Access-Control-Max-Age': '86400', }; -export async function onRequestPost(context) { +export async function onRequest(context) { + if (context.request.method !== 'POST') { + return jsonResponse({ success: false, error: 'Method not allowed' }, 405); + } + try { const payload = await context.request.json(); const fileIds = normalizeBatchFileIds(payload?.fileIds, MAX_BATCH_SIZE); @@ -21,9 +25,13 @@ export async function onRequestPost(context) { } const url = new URL(context.request.url); const results = await mapConcurrent(fileIds, DELETE_CONCURRENCY, async (fileId) => { - const cdnUrl = `${url.origin}/file/${fileId.split('/').map(encodeURIComponent).join('/')}`; - const success = await deleteFile(context.env, fileId, cdnUrl, url); - return { fileId, success, error: success ? '' : 'Delete file failed' }; + try { + const cdnUrl = `${url.origin}/file/${fileId.split('/').map(encodeURIComponent).join('/')}`; + const success = await deleteFile(context.env, fileId, cdnUrl, url); + return { fileId, success, error: success ? '' : 'Delete file failed' }; + } catch (err) { + return { fileId, success: false, error: String(err?.message || err) }; + } }); const deleted = results.filter((item) => item.success).map((item) => item.fileId); diff --git a/functions/utils/deleteBatch.js b/functions/utils/deleteBatch.js index e4689958..c0ce06f9 100644 --- a/functions/utils/deleteBatch.js +++ b/functions/utils/deleteBatch.js @@ -1,8 +1,20 @@ export function normalizeBatchFileIds(values, maxBatchSize) { if (!Array.isArray(values)) return []; - const normalized = [...new Set(values - .map((value) => String(value || '').trim().replace(/^\/+|\/+$/g, '')) - .filter(Boolean))]; + const normalized = []; + const seen = new Set(); + + for (const value of values) { + if (typeof value !== 'string') { + throw new Error('fileIds must be an array of strings'); + } + + const fileId = value.trim().replace(/^\/+|\/+$/g, ''); + if (fileId && !seen.has(fileId)) { + seen.add(fileId); + normalized.push(fileId); + } + } + if (normalized.length > maxBatchSize) { throw new Error(`A maximum of ${maxBatchSize} files can be deleted at once`); } @@ -11,8 +23,14 @@ export function normalizeBatchFileIds(values, maxBatchSize) { export async function mapConcurrent(values, concurrency, operation) { const results = new Array(values.length); + if (values.length === 0) return results; + + const workerCount = Math.min( + Math.max(1, Math.floor(Number(concurrency) || 1)), + values.length + ); let nextIndex = 0; - const workers = Array.from({ length: Math.min(concurrency, values.length) }, async () => { + const workers = Array.from({ length: workerCount }, async () => { while (nextIndex < values.length) { const index = nextIndex++; results[index] = await operation(values[index]);