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; diff --git a/src/ParseFile.js b/src/ParseFile.js index 9470b4813..0cc4e7d62 100644 --- a/src/ParseFile.js +++ b/src/ParseFile.js @@ -42,7 +42,15 @@ export type FileSource = type: string, }; -const dataUriRegexp = /^data:([a-zA-Z]+\/[-a-zA-Z0-9+.]+)(;charset=[a-zA-Z0-9\-\/]*)?;base64,/; +const base64Regex = new RegExp( + '([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))', + 'i' +); + +const dataUriRegex = new RegExp( + `^data:([a-zA-Z]+\\/[-a-zA-Z0-9+.]+(;[a-z-]+=[a-zA-Z0-9+.-]+)?)?(;base64)?,(${base64Regex.source})*$`, + 'i' +); function b64Digit(number: number): string { if (number < 26) { @@ -137,26 +145,30 @@ 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, - }; + // 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( + 'Cannot create a Parse.File without valid data URIs or base64 encoded data.' + ); + } + + 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. + // As a shorthand, "text/plain" can be omitted but the charset parameter supplied. + if (dataUriRegex.test(data.base64)) { + type = type || 'text/plain'; } + + this._source = { + format: 'base64', + base64, + type, + }; } else { throw new TypeError('Cannot create a Parse.File with that data.'); } diff --git a/src/__tests__/ParseFile-test.js b/src/__tests__/ParseFile-test.js index 631a6c108..fb045dbc3 100644 --- a/src/__tests__/ParseFile-test.js +++ b/src/__tests__/ParseFile-test.js @@ -66,14 +66,30 @@ describe('ParseFile', () => { expect(file._source.type).toBe(''); }); - it('can extract data type from base64', () => { + 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==', }); expect(file._source.base64).toBe('ParseA=='); expect(file._source.type).toBe('image/png'); }); + 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(file._source.base64).toBe('ParseA=='); + expect(file._source.type).toBe('application/pdf'); + }); + it('can create files with file uri', () => { const file = new ParseFile('parse-image', { uri: 'http://example.com/image.png', @@ -136,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', () => {