From 64dfe6432e26713df7ac29f2e480061c19f8a66a Mon Sep 17 00:00:00 2001 From: trevor-signal <131492920+trevor-signal@users.noreply.github.com> Date: Mon, 11 Aug 2025 15:21:21 -0400 Subject: [PATCH] Treat 403 from CDN0 the same as a 404 from others CDNs --- .../util/downloadAttachment_test.ts | 61 +++++++++++++++++++ ts/util/downloadAttachment.ts | 7 +++ 2 files changed, 68 insertions(+) diff --git a/ts/test-electron/util/downloadAttachment_test.ts b/ts/test-electron/util/downloadAttachment_test.ts index 7da302aeb3..301a048047 100644 --- a/ts/test-electron/util/downloadAttachment_test.ts +++ b/ts/test-electron/util/downloadAttachment_test.ts @@ -122,6 +122,67 @@ describe('utils/downloadAttachment', () => { ]); }); + it('throw permanently missing error if attachment fails with 403 from cdn 0 and no backup information', async () => { + const stubDownload = sinon + .stub() + .onFirstCall() + .throws(new HTTPError('not found', { code: 403, headers: {} })); + + const attachment = { ...baseAttachment, cdnNumber: 0 }; + await assert.isRejected( + downloadAttachment({ + attachment, + options: { + hasMediaBackups: true, + onSizeUpdate: noop, + abortSignal: abortController.signal, + }, + dependencies: { + downloadAttachmentFromLocalBackup: stubDownload, + downloadAttachmentFromServer: stubDownload, + }, + }), + AttachmentPermanentlyUndownloadableError + ); + + assert.equal(stubDownload.callCount, 1); + assertDownloadArgs(stubDownload.getCall(0).args, [ + fakeServer, + { attachment, mediaTier: MediaTier.STANDARD }, + { + variant: AttachmentVariant.Default, + onSizeUpdate: noop, + abortSignal: abortController.signal, + logPrefix: '[REDACTED]est', + }, + ]); + }); + + it('throw permanently missing error if attachment fails with 403 with no cdn number and no backup information', async () => { + const stubDownload = sinon + .stub() + .onFirstCall() + .throws(new HTTPError('not found', { code: 403, headers: {} })); + + // nullish cdn number gets converted to 0 + const attachment = { ...baseAttachment, cdnNumber: undefined }; + await assert.isRejected( + downloadAttachment({ + attachment, + options: { + hasMediaBackups: true, + onSizeUpdate: noop, + abortSignal: abortController.signal, + }, + dependencies: { + downloadAttachmentFromLocalBackup: stubDownload, + downloadAttachmentFromServer: stubDownload, + }, + }), + AttachmentPermanentlyUndownloadableError + ); + }); + it('downloads from backup tier first if there is backup information', async () => { const stubDownload = sinon.stub(); const attachment = backupableAttachment; diff --git a/ts/util/downloadAttachment.ts b/ts/util/downloadAttachment.ts index 725fb4c066..fd020edc63 100644 --- a/ts/util/downloadAttachment.ts +++ b/ts/util/downloadAttachment.ts @@ -138,6 +138,13 @@ export async function downloadAttachment({ // then start returning 404 if (error instanceof HTTPError && error.code === 404) { throw new AttachmentPermanentlyUndownloadableError(`HTTP ${error.code}`); + } else if ( + error instanceof HTTPError && + // CDN 0 can return 403 which means the same as 404 from other CDNs + error.code === 403 && + (attachment.cdnNumber == null || attachment.cdnNumber === 0) + ) { + throw new AttachmentPermanentlyUndownloadableError(`HTTP ${error.code}`); } else { throw error; }