From 861a6d01c057642f94d045f26be631104127c3f4 Mon Sep 17 00:00:00 2001 From: smarcet Date: Thu, 20 Aug 2026 18:13:53 -0300 Subject: [PATCH] fix: encode fileId before building the upload status polling URL pollUploadStatus interpolated the server-provided fileId directly into GET .../status/ without encoding it. file-upload-api's simple (non-chunked) upload path derives file_id from the raw original filename, so a name like "...20x10ft@50%_Ver1.4...pdf" produced a malformed URL (invalid percent-escape) - the request failed, surfaced in the browser as an opaque CORS error, and the upload got stuck in "Loading" forever. encodeURIComponent the fileId regardless of what the server sends. --- .../dropzone/__tests__/dropzone.test.js | 53 +++++++++++++++++++ src/components/inputs/dropzone/index.js | 4 +- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/components/inputs/dropzone/__tests__/dropzone.test.js b/src/components/inputs/dropzone/__tests__/dropzone.test.js index d776708e..39ece6d3 100644 --- a/src/components/inputs/dropzone/__tests__/dropzone.test.js +++ b/src/components/inputs/dropzone/__tests__/dropzone.test.js @@ -232,6 +232,59 @@ describe('DropzoneJS - HTTP 202 Polling UX', () => { }, 2500); }, 10); }, 10000); + + /** + * Test Case 5: pollUploadStatus must percent-encode fileId in the status URL + * + * For a simple (non-chunked) upload, file-upload-api derives file_id from the raw + * original filename (e.g. "...20x10ft@50%_Ver1.4...pdf_"). Interpolating + * that straight into the status URL produces an invalid percent-escape ('%_') that + * breaks the request. Encode it regardless of what the server sends. + */ + test('test_dropzone_poll_upload_status_encodes_file_id_in_url', (done) => { + global.fetch = jest.fn(() => + Promise.resolve({ + json: () => Promise.resolve({ + status: 'complete', + name: 'HPE_OCPSanJose_Backdrop_20x10ft50_Ver1.4_PRINT.pdf', + size: 1024000 + }) + }) + ); + + const ref = React.createRef(); + + render( + + ); + + setTimeout(() => { + const mockFile = { + name: 'HPE_OCPSanJose_Backdrop_20x10ft@50%_Ver1.4_PRINT.pdf', + size: 1024000, + _asyncProcessing: true, + _chunksUploadedDone: jest.fn() + }; + const unsafeFileId = 'HPE_OCPSanJose_Backdrop_20x10ft@50%_Ver1.4_PRINT.pdf_1787259323'; + + const instance = ref.current; + instance.pollUploadStatus(unsafeFileId, 'https://example.com/upload', mockFile); + + setTimeout(() => { + expect(global.fetch).toHaveBeenCalledWith( + 'https://example.com/upload/status/HPE_OCPSanJose_Backdrop_20x10ft%4050%25_Ver1.4_PRINT.pdf_1787259323', + expect.any(Object) + ); + + done(); + }, 2500); + }, 10); + }, 10000); }); describe('DropzoneJS - Progress Bar Monotonicity', () => { diff --git a/src/components/inputs/dropzone/index.js b/src/components/inputs/dropzone/index.js index 4b48cca9..f41f9990 100644 --- a/src/components/inputs/dropzone/index.js +++ b/src/components/inputs/dropzone/index.js @@ -71,7 +71,9 @@ export class DropzoneJS extends React.Component { } file._pollingActive = true; - const statusUrl = `${baseUrl}/status/${fileId}`; + // fileId is interpolated straight into the URL path, so it must be encoded even + // when the server sanitizes it - defends against any unsafe character reaching here. + const statusUrl = `${baseUrl}/status/${encodeURIComponent(fileId)}`; const maxAttempts = 300; // 10 minutes at 2s intervals let attempts = 0;