diff --git a/src/puter-js/src/modules/FileSystem/operations/upload/dataUrl.js b/src/puter-js/src/modules/FileSystem/operations/upload/dataUrl.js index 431dddfa0..1d34a0ebf 100644 --- a/src/puter-js/src/modules/FileSystem/operations/upload/dataUrl.js +++ b/src/puter-js/src/modules/FileSystem/operations/upload/dataUrl.js @@ -11,7 +11,8 @@ export const estimateDataUrlSize = (dataUrl) => { if ( ! dataUrl ) return 0; const commaIndex = dataUrl.indexOf(','); const base64 = commaIndex === -1 ? dataUrl : dataUrl.slice(commaIndex + 1); - return Math.ceil(base64.length * 3 / 4); + const padding = base64.endsWith('==') ? 2 : (base64.endsWith('=') ? 1 : 0); + return Math.floor(base64.length * 3 / 4) - padding; }; /** diff --git a/src/puter-js/src/modules/FileSystem/operations/upload/dataUrl.test.js b/src/puter-js/src/modules/FileSystem/operations/upload/dataUrl.test.js new file mode 100644 index 000000000..90dc7ba5c --- /dev/null +++ b/src/puter-js/src/modules/FileSystem/operations/upload/dataUrl.test.js @@ -0,0 +1,31 @@ +import { describe, expect, it } from 'vitest'; + +import { estimateDataUrlSize } from './dataUrl.js'; + +// `estimateDataUrlSize` gates thumbnail uploads, so it must return decoded +// bytes — not `ceil(base64.length * 3 / 4)`, which over-counts by 1-2 bytes +// whenever the payload ends in `=` padding. +describe('estimateDataUrlSize', () => { + it('returns 0 for empty input', () => { + expect(estimateDataUrlSize('')).toBe(0); + }); + + it('sizes an unpadded payload', () => { + // 'ABC' -> 'QUJD' + expect(estimateDataUrlSize('data:text/plain;base64,QUJD')).toBe(3); + }); + + it('subtracts single `=` padding', () => { + // 'AB' -> 'QUI=' + expect(estimateDataUrlSize('data:text/plain;base64,QUI=')).toBe(2); + }); + + it('subtracts double `==` padding', () => { + // 'A' -> 'QQ==' + expect(estimateDataUrlSize('data:text/plain;base64,QQ==')).toBe(1); + }); + + it('sizes raw base64 without a data URL prefix', () => { + expect(estimateDataUrlSize('QUI=')).toBe(2); + }); +});