From 4e70741597a39f585db59386e87efee3e1a453e4 Mon Sep 17 00:00:00 2001 From: Razvan Radulescu Date: Tue, 29 Mar 2022 20:02:03 +0300 Subject: [PATCH 01/14] Fix base64 formatting --- src/ParseFile.js | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/src/ParseFile.js b/src/ParseFile.js index 9470b4813..6ee039bcb 100644 --- a/src/ParseFile.js +++ b/src/ParseFile.js @@ -42,8 +42,6 @@ export type FileSource = type: string, }; -const dataUriRegexp = /^data:([a-zA-Z]+\/[-a-zA-Z0-9+.]+)(;charset=[a-zA-Z0-9\-\/]*)?;base64,/; - function b64Digit(number: number): string { if (number < 26) { return String.fromCharCode(65 + number); @@ -137,26 +135,18 @@ class ParseFile { type: specifiedType, }; } else if (data && typeof data.base64 === 'string') { - const base64 = data.base64; - const commaIndex = base64.indexOf(','); - - if (commaIndex !== -1) { - const matches = dataUriRegexp.exec(base64.slice(0, commaIndex + 1)); - // if data URI with type and charset, there will be 4 matches. - this._data = base64.slice(commaIndex + 1); - this._source = { - format: 'base64', - base64: this._data, - type: matches[1], - }; - } else { - this._data = base64; - this._source = { - format: 'base64', - base64: base64, - type: specifiedType, - }; + const base64 = data.base64.split(',').at(-1) + const type = specifiedType || data.split(':').at(1).split(';').at(0) + + if (!type) { + throw new Error('File must have a valid type.'); } + + this._source = { + format: 'base64', + base64, + type, + }; } else { throw new TypeError('Cannot create a Parse.File with that data.'); } From a05f68135e9c0f99809ea460c6c79bfe06e1e0db Mon Sep 17 00:00:00 2001 From: Razvan Radulescu Date: Tue, 29 Mar 2022 20:11:17 +0300 Subject: [PATCH 02/14] Add new test for base64 formatting --- src/__tests__/ParseFile-test.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/__tests__/ParseFile-test.js b/src/__tests__/ParseFile-test.js index 631a6c108..39263391a 100644 --- a/src/__tests__/ParseFile-test.js +++ b/src/__tests__/ParseFile-test.js @@ -67,11 +67,17 @@ describe('ParseFile', () => { }); it('can extract data type from base64', () => { - const file = new ParseFile('parse.txt', { + const file1 = new ParseFile('parse.txt', { base64: 'data:image/png;base64,ParseA==', }); - expect(file._source.base64).toBe('ParseA=='); - expect(file._source.type).toBe('image/png'); + expect(file1._source.base64).toBe('ParseA=='); + expect(file1._source.type).toBe('image/png'); + + const file2 = new ParseFile('parse.txt', { + base64: 'data:application/pdf;filename=test.pdf;base64,ParseA==', + }); + expect(file2._source.base64).toBe('ParseA=='); + expect(file2._source.type).toBe('application/pdf'); }); it('can create files with file uri', () => { From 2c94df3f27174018157cdef7bff497a78a43f42a Mon Sep 17 00:00:00 2001 From: Razvan Radulescu Date: Tue, 29 Mar 2022 20:13:57 +0300 Subject: [PATCH 03/14] Update changelog #1467 --- changelogs/CHANGELOG_release.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelogs/CHANGELOG_release.md b/changelogs/CHANGELOG_release.md index 560c9424d..d8b53e680 100644 --- a/changelogs/CHANGELOG_release.md +++ b/changelogs/CHANGELOG_release.md @@ -54,6 +54,8 @@ for (const className of classNames) { ### Bug Fixes - Fixes build for WeChat WeApp, to reduce package size, see [issue/#1331](https://github.com/parse-community/Parse-SDK-JS/issues/1331) +- Fixes base64 formatting, see [issue/#1467](https://github.com/parse-community/Parse-SDK-JS/pull/1467) + # [3.1.0](https://github.com/parse-community/Parse-SDK-JS/compare/3.0.0...3.1.0) ### Breaking Changes From 9d462f3604785a289e3ccb01b5188bd8286d8222 Mon Sep 17 00:00:00 2001 From: Razvan Radulescu Date: Thu, 31 Mar 2022 15:31:11 +0300 Subject: [PATCH 04/14] Revert changelog changes --- changelogs/CHANGELOG_release.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/changelogs/CHANGELOG_release.md b/changelogs/CHANGELOG_release.md index d8b53e680..560c9424d 100644 --- a/changelogs/CHANGELOG_release.md +++ b/changelogs/CHANGELOG_release.md @@ -54,8 +54,6 @@ for (const className of classNames) { ### Bug Fixes - Fixes build for WeChat WeApp, to reduce package size, see [issue/#1331](https://github.com/parse-community/Parse-SDK-JS/issues/1331) -- Fixes base64 formatting, see [issue/#1467](https://github.com/parse-community/Parse-SDK-JS/pull/1467) - # [3.1.0](https://github.com/parse-community/Parse-SDK-JS/compare/3.0.0...3.1.0) ### Breaking Changes From 17f70744f1ead0f8780a9f2ac9f98798eae15729 Mon Sep 17 00:00:00 2001 From: Razvan Radulescu Date: Thu, 31 Mar 2022 16:12:58 +0300 Subject: [PATCH 05/14] Deleted unnecessary throw when the filetype is not specified --- src/ParseFile.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/ParseFile.js b/src/ParseFile.js index 6ee039bcb..a0575a3b9 100644 --- a/src/ParseFile.js +++ b/src/ParseFile.js @@ -135,12 +135,8 @@ class ParseFile { type: specifiedType, }; } else if (data && typeof data.base64 === 'string') { - const base64 = data.base64.split(',').at(-1) - const type = specifiedType || data.split(':').at(1).split(';').at(0) - - if (!type) { - throw new Error('File must have a valid type.'); - } + const base64 = data.base64.split(',').at(-1); + const type = specifiedType || data.base64.split(';').at(0).split(':').at(1) || ''; this._source = { format: 'base64', From 50728e46ab2825b5334e576c8a6dfe6b857d51c2 Mon Sep 17 00:00:00 2001 From: Razvan Radulescu Date: Thu, 31 Mar 2022 16:14:16 +0300 Subject: [PATCH 06/14] Added test for base64 file with filename parameter --- src/__tests__/ParseFile-test.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/__tests__/ParseFile-test.js b/src/__tests__/ParseFile-test.js index 39263391a..b100ba6f2 100644 --- a/src/__tests__/ParseFile-test.js +++ b/src/__tests__/ParseFile-test.js @@ -67,17 +67,19 @@ describe('ParseFile', () => { }); it('can extract data type from base64', () => { - const file1 = new ParseFile('parse.txt', { + const file = new ParseFile('parse.png', { base64: 'data:image/png;base64,ParseA==', }); - expect(file1._source.base64).toBe('ParseA=='); - expect(file1._source.type).toBe('image/png'); + expect(file._source.base64).toBe('ParseA=='); + expect(file._source.type).toBe('image/png'); + }); - const file2 = new ParseFile('parse.txt', { - base64: 'data:application/pdf;filename=test.pdf;base64,ParseA==', + it('can extract data type from base64 with a filename parameter', () => { + const file = new ParseFile('parse.pdf', { + base64: 'data:application/pdf;filename=parse.pdf;base64,ParseA==', }); - expect(file2._source.base64).toBe('ParseA=='); - expect(file2._source.type).toBe('application/pdf'); + expect(file._source.base64).toBe('ParseA=='); + expect(file._source.type).toBe('application/pdf'); }); it('can create files with file uri', () => { From e13000e5d8218b64fd72d8def8c82519f77d84a1 Mon Sep 17 00:00:00 2001 From: Razvan Radulescu Date: Thu, 31 Mar 2022 20:44:27 +0300 Subject: [PATCH 07/14] Add validation regex --- src/ParseFile.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/ParseFile.js b/src/ParseFile.js index a0575a3b9..c05491e99 100644 --- a/src/ParseFile.js +++ b/src/ParseFile.js @@ -42,6 +42,16 @@ export type FileSource = type: string, }; +const base64Regex = new RegExp( + '([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))', + 'i' +); + +const dataUriRegex = new RegExp( + `^\\s*data:([a-zA-Z]+\\/[-a-zA-Z0-9+.]+(;[a-z-]+=[a-zA-Z0-9+.-]+)?)?(;base64)?,(${base64Regex.source})*\\s*$`, + 'i' +); + function b64Digit(number: number): string { if (number < 26) { return String.fromCharCode(65 + number); @@ -135,6 +145,12 @@ class ParseFile { type: specifiedType, }; } else if (data && typeof data.base64 === 'string') { + // Check if data URI or base64 string is valid + const validationRegex = new RegExp(base64Regex.source + '|' + dataUriRegex.source, 'i'); + if (!validationRegex.test(data.base64)) { + throw new Error('Files passed in must have valid data URIs or base64 encoded data.'); + } + const base64 = data.base64.split(',').at(-1); const type = specifiedType || data.base64.split(';').at(0).split(':').at(1) || ''; From 5225436ba4d0bf1fd4d4aa5e1b77ef6799c9f105 Mon Sep 17 00:00:00 2001 From: Razvan Radulescu Date: Thu, 31 Mar 2022 20:57:42 +0300 Subject: [PATCH 08/14] Remove whitespace in regex --- src/ParseFile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ParseFile.js b/src/ParseFile.js index c05491e99..c42ea260b 100644 --- a/src/ParseFile.js +++ b/src/ParseFile.js @@ -48,7 +48,7 @@ const base64Regex = new RegExp( ); const dataUriRegex = new RegExp( - `^\\s*data:([a-zA-Z]+\\/[-a-zA-Z0-9+.]+(;[a-z-]+=[a-zA-Z0-9+.-]+)?)?(;base64)?,(${base64Regex.source})*\\s*$`, + `^data:([a-zA-Z]+\\/[-a-zA-Z0-9+.]+(;[a-z-]+=[a-zA-Z0-9+.-]+)?)?(;base64)?,(${base64Regex.source})*$`, 'i' ); From bbf960a969f00dc0768e8815781505340a4ed8c1 Mon Sep 17 00:00:00 2001 From: Razvan Radulescu Date: Fri, 1 Apr 2022 20:58:32 +0300 Subject: [PATCH 09/14] Default type text/plain --- src/ParseFile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ParseFile.js b/src/ParseFile.js index c42ea260b..bfa2ba2e3 100644 --- a/src/ParseFile.js +++ b/src/ParseFile.js @@ -152,7 +152,7 @@ class ParseFile { } const base64 = data.base64.split(',').at(-1); - const type = specifiedType || data.base64.split(';').at(0).split(':').at(1) || ''; + const type = specifiedType || data.base64.split(';').at(0).split(':').at(1) || 'text/plain'; this._source = { format: 'base64', From 75dea60a5923a4305256dc07c9e3896d4176e315 Mon Sep 17 00:00:00 2001 From: Razvan Radulescu Date: Fri, 1 Apr 2022 21:12:42 +0300 Subject: [PATCH 10/14] Rework; Update tests --- src/ParseFile.js | 9 ++++++++- src/__tests__/ParseFile-test.js | 8 ++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/ParseFile.js b/src/ParseFile.js index bfa2ba2e3..3ee111e6b 100644 --- a/src/ParseFile.js +++ b/src/ParseFile.js @@ -152,7 +152,14 @@ class ParseFile { } const base64 = data.base64.split(',').at(-1); - const type = specifiedType || data.base64.split(';').at(0).split(':').at(1) || 'text/plain'; + let type = specifiedType || data.base64.split(';').at(0).split(':').at(1) || ''; + + // https://tools.ietf.org/html/rfc2397 + // If is omitted, it defaults to text/plain;charset=US-ASCII. + // As a shorthand, "text/plain" can be omitted but the charset parameter supplied. + if (dataUriRegex.test(data.base64)) { + type ||= 'text/plain'; + } this._source = { format: 'base64', diff --git a/src/__tests__/ParseFile-test.js b/src/__tests__/ParseFile-test.js index b100ba6f2..7dc7a96bd 100644 --- a/src/__tests__/ParseFile-test.js +++ b/src/__tests__/ParseFile-test.js @@ -66,6 +66,14 @@ describe('ParseFile', () => { expect(file._source.type).toBe(''); }); + it('can set the default type to be text/plain when using base64', () => { + const file = new ParseFile('parse.txt', { + base64: 'data:;base64,ParseA==', + }); + expect(file._source.base64).toBe('ParseA=='); + expect(file._source.type).toBe('text/plain'); + }); + it('can extract data type from base64', () => { const file = new ParseFile('parse.png', { base64: 'data:image/png;base64,ParseA==', From 7b47bdd0047476ff5d30e8ab0fc53296b377cb0e Mon Sep 17 00:00:00 2001 From: Razvan Radulescu Date: Mon, 4 Apr 2022 20:31:12 +0300 Subject: [PATCH 11/14] Replace ||= operator --- src/ParseFile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ParseFile.js b/src/ParseFile.js index 3ee111e6b..146b68935 100644 --- a/src/ParseFile.js +++ b/src/ParseFile.js @@ -158,7 +158,7 @@ class ParseFile { // If is omitted, it defaults to text/plain;charset=US-ASCII. // As a shorthand, "text/plain" can be omitted but the charset parameter supplied. if (dataUriRegex.test(data.base64)) { - type ||= 'text/plain'; + type = type || 'text/plain'; } this._source = { From 9082545919c9856dbe3da9914d69d78e002ee884 Mon Sep 17 00:00:00 2001 From: Razvan Radulescu Date: Tue, 19 Apr 2022 00:06:00 +0300 Subject: [PATCH 12/14] Replace .at with .splice --- src/ParseFile.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ParseFile.js b/src/ParseFile.js index 146b68935..dad0d58bd 100644 --- a/src/ParseFile.js +++ b/src/ParseFile.js @@ -151,8 +151,9 @@ class ParseFile { throw new Error('Files passed in must have valid data URIs or base64 encoded data.'); } - const base64 = data.base64.split(',').at(-1); - let type = specifiedType || data.base64.split(';').at(0).split(':').at(1) || ''; + const base64 = data.base64.split(',').slice(-1)[0]; + let type = + specifiedType || data.base64.split(';').slice(0, 1)[0].split(':').slice(1, 2)[0] || ''; // https://tools.ietf.org/html/rfc2397 // If is omitted, it defaults to text/plain;charset=US-ASCII. From 02b9a798f2061265f1feaf000da6137b40326185 Mon Sep 17 00:00:00 2001 From: Razvan Radulescu Date: Wed, 20 Apr 2022 08:34:50 +0300 Subject: [PATCH 13/14] Test changes --- integration/test/ParseFileTest.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/integration/test/ParseFileTest.js b/integration/test/ParseFileTest.js index 1cecbb07a..c594ff3d3 100644 --- a/integration/test/ParseFileTest.js +++ b/integration/test/ParseFileTest.js @@ -66,6 +66,7 @@ describe('Parse.File', () => { it('can get file data from base64', async () => { const file = new Parse.File('parse-server-logo', { base64: 'ParseA==' }); + await file.save(); let data = await file.getData(); assert.equal(data, 'ParseA=='); file._data = null; @@ -79,6 +80,7 @@ describe('Parse.File', () => { const file = new Parse.File('parse-server-logo', { base64: 'data:image/jpeg;base64,ParseA==', }); + await file.save(); let data = await file.getData(); assert.equal(data, 'ParseA=='); file._data = null; From a368cbc57482a4cd78a0278dcaa9129c8c6d99e2 Mon Sep 17 00:00:00 2001 From: Razvan Radulescu Date: Thu, 28 Apr 2022 22:33:34 +0300 Subject: [PATCH 14/14] Add new test case; rephrase error message --- src/ParseFile.js | 4 +++- src/__tests__/ParseFile-test.js | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/ParseFile.js b/src/ParseFile.js index dad0d58bd..0cc4e7d62 100644 --- a/src/ParseFile.js +++ b/src/ParseFile.js @@ -148,7 +148,9 @@ class ParseFile { // Check if data URI or base64 string is valid const validationRegex = new RegExp(base64Regex.source + '|' + dataUriRegex.source, 'i'); if (!validationRegex.test(data.base64)) { - throw new Error('Files passed in must have valid data URIs or base64 encoded data.'); + throw new Error( + 'Cannot create a Parse.File without valid data URIs or base64 encoded data.' + ); } const base64 = data.base64.split(',').slice(-1)[0]; diff --git a/src/__tests__/ParseFile-test.js b/src/__tests__/ParseFile-test.js index 7dc7a96bd..fb045dbc3 100644 --- a/src/__tests__/ParseFile-test.js +++ b/src/__tests__/ParseFile-test.js @@ -152,6 +152,12 @@ describe('ParseFile', () => { expect(function () { new ParseFile('parse.txt', 'string'); }).toThrow('Cannot create a Parse.File with that data.'); + + expect(function () { + new ParseFile('parse.txt', { + base64: 'abc', + }); + }).toThrow('Cannot create a Parse.File without valid data URIs or base64 encoded data.'); }); it('throws with invalid base64', () => {