From 8c481a1564f674ee8243a6a92a65b711d6e290b1 Mon Sep 17 00:00:00 2001 From: shuaiplus <2327005759@qq.com> Date: Sun, 5 Jul 2026 23:44:07 +0800 Subject: [PATCH] fix(send): refresh routes and gate file access --- src/handlers/sends-private.ts | 8 +++++++- src/handlers/sends-public.ts | 12 ++++++++++-- src/services/storage-send-repo.ts | 8 ++++++-- webapp/src/App.tsx | 15 ++++++++------- 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/handlers/sends-private.ts b/src/handlers/sends-private.ts index ddd91a4..2433fce 100644 --- a/src/handlers/sends-private.ts +++ b/src/handlers/sends-private.ts @@ -8,6 +8,7 @@ import { LIMITS } from '../config/limits'; import { getBlobStorageMaxBytes, getSendFileObjectKey, + getBlobObject, putBlobObject, deleteBlobObject, } from '../services/blob-store'; @@ -82,8 +83,13 @@ async function processSendFileUpload( return upload; } + const path = getSendFileObjectKey(send.id, fileId); + if (await getBlobObject(env, path)) { + return errorResponse('Send file has already been uploaded', 409); + } + try { - await putBlobObject(env, getSendFileObjectKey(send.id, fileId), upload.body, { + await putBlobObject(env, path, upload.body, { size: upload.size, contentType: upload.contentType, customMetadata: { diff --git a/src/handlers/sends-public.ts b/src/handlers/sends-public.ts index 1b171d0..5e353f7 100644 --- a/src/handlers/sends-public.ts +++ b/src/handlers/sends-public.ts @@ -290,12 +290,20 @@ export async function handleDownloadSendFile( } const storage = new StorageService(env.DB); + const send = await storage.getSend(sendId); + if (!send || !isSendAvailable(send) || send.type !== SendType.File) { + return errorResponse(SEND_INACCESSIBLE_MSG, 404); + } + const data = parseStoredSendData(send); + const expectedFileId = typeof data.id === 'string' ? data.id : null; + if (!expectedFileId || expectedFileId !== fileId) { + return errorResponse(SEND_INACCESSIBLE_MSG, 404); + } + const object = await getBlobObject(env, getSendFileObjectKey(sendId, fileId)); if (!object) { return errorResponse('Send file not found', 404); } - const send = await storage.getSend(sendId); - const data = send ? parseStoredSendData(send) : {}; const fileName = typeof data.fileName === 'string' ? data.fileName : fileId; const firstUse = await storage.consumeAttachmentDownloadToken(`send:${claims.jti}`, claims.exp); diff --git a/src/services/storage-send-repo.ts b/src/services/storage-send-repo.ts index f02a72a..726b1b1 100644 --- a/src/services/storage-send-repo.ts +++ b/src/services/storage-send-repo.ts @@ -93,9 +93,13 @@ export async function incrementSendAccessCount(db: D1Database, sendId: string): const result = await db .prepare( 'UPDATE sends SET access_count = access_count + 1, updated_at = ? ' + - 'WHERE id = ? AND (max_access_count IS NULL OR access_count < max_access_count)' + 'WHERE id = ? ' + + 'AND disabled = 0 ' + + 'AND (max_access_count IS NULL OR access_count < max_access_count) ' + + 'AND (expiration_date IS NULL OR expiration_date > ?) ' + + 'AND deletion_date > ?' ) - .bind(now, sendId) + .bind(now, sendId, now, now) .run(); return (result.meta.changes ?? 0) > 0; } diff --git a/webapp/src/App.tsx b/webapp/src/App.tsx index a477709..066b065 100644 --- a/webapp/src/App.tsx +++ b/webapp/src/App.tsx @@ -228,6 +228,7 @@ export default function App() { hint: null, }); const [inviteCodeFromUrl, setInviteCodeFromUrl] = useState(initialInviteCode); + const [hashPathRaw, setHashPathRaw] = useState(() => (typeof window !== 'undefined' ? window.location.hash || '' : '')); const [unlockPassword, setUnlockPassword] = useState(''); const [pendingTotp, setPendingTotp] = useState(null); const [pendingTotpMode, setPendingTotpMode] = useState<'login' | 'unlock' | null>(null); @@ -295,15 +296,16 @@ export default function App() { }, [pushToast]); useEffect(() => { - const syncInviteFromUrl = () => { + const syncUrlState = () => { setInviteCodeFromUrl(readInviteCodeFromUrl()); + setHashPathRaw(window.location.hash || ''); }; - syncInviteFromUrl(); - window.addEventListener('hashchange', syncInviteFromUrl); - window.addEventListener('popstate', syncInviteFromUrl); + syncUrlState(); + window.addEventListener('hashchange', syncUrlState); + window.addEventListener('popstate', syncUrlState); return () => { - window.removeEventListener('hashchange', syncInviteFromUrl); - window.removeEventListener('popstate', syncInviteFromUrl); + window.removeEventListener('hashchange', syncUrlState); + window.removeEventListener('popstate', syncUrlState); }; }, []); @@ -1862,7 +1864,6 @@ export default function App() { await pendingAuthRequestsQuery.refetch(); }; - const hashPathRaw = typeof window !== 'undefined' ? window.location.hash || '' : ''; const hashPath = hashPathRaw.startsWith('#') ? hashPathRaw.slice(1) : hashPathRaw; const hashPathOnly = String(hashPath || '').split('?')[0].split('#')[0]; const trimmedHashPath = hashPathOnly.replace(/^\/+/, '').replace(/\/+$/, '');