Repository: parse-community/Parse-SDK-JS (js)

Issue:
Issue uploading files
### New Issue Checklist
- [x] I am not disclosing a [vulnerability](https://github.com/parse-community/parse-server/blob/master/SECURITY.md).
- [x] I am not just asking a [question](https://github.com/parse-community/.github/blob/master/SUPPORT.md).
- [x] I have searched through [existing issues](https://github.com/parse-community/parse-server/issues?q=is%3Aissue).
- [x] I can reproduce the issue with the [latest version of Parse Server](https://github.com/parse-community/parse-server/releases). <!-- We don't investigate issues for outdated releases. -->

### Issue Description
Parse server throws the following error while uploading files:
`
The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined 
`

### Steps to reproduce
Using File API of Parse  SDK:
```
const file = new Parse.File('parse-server-logo', {
  base64: 'data:image/jpeg;base64,ParseA==',
});
await file.save();
```

Using File upload Rest API:
```
curl --location --request POST 'http://198.199.84.19:1337/parse/files/test.txt' \
--header 'X-Parse-Application-Id: <APP_ID>' \
--header 'X-Parse-REST-API-Key: <REST_API_KEY>' \
--header 'X-Parse-Master-Key: <MASTER_KEY>' \
--header 'Content-Type: application/text' \
--data-raw 'Hello world' 
```

### Actual Outcome
For File API of Parse SDK:
```
The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined
```

For File upload Rest API:
```
{
    "code": 130,
    "error": "The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined"
}
```

### Expected Outcome
Successfully upload the file and respond with the URL to the file. A sample success response would be:
```
{
    "url": "http://localhost:1337/parse/files/APP_ID/0fef055962401a0c5d23d9c10b737a07_test.txt",
    "name": "0fef055962401a0c5d23d9c10b737a07_test.txt"
}
```

### Environment
This issue occurs with Parse SDK version `^3.5.0-alpha.6`. Please note that the same version of Parse SDK must be used in Parse Server. Parse Server uses the latest released version of Parse SDK (3.4.2) by default. You will need to override the SDK version for Parse server. To do so, you can add the following in `package.json`:
```
"overrides": {
  "parse-server": {
    "parse": "^3.5.0-alpha.6"
  }
}
```

Also note that npm package overriding is only supported from npm version >=8.3.0.

Server
- Parse Server version: `5.2.8`, `5.3.0-alpha.30`
- Operating system: `macOS Monterey`, `Ubuntu 22.04.1 LTS`
- Local or remote host (AWS, Azure, Google Cloud, Heroku, Digital Ocean, etc): `local`. `Digital Ocean`

Database
- System (MongoDB or Postgres): `Postgres`
- Database version: `PostgreSQL 14.1`
- Local or remote host (MongoDB Atlas, mLab, AWS, Azure, Google Cloud, etc): `local`, 'Digital Ocean'

Client
- SDK (iOS, Android, JavaScript, PHP, Unity, etc): `Javascript`
- SDK version: `^3.5.0-alpha.6`

### Logs
`
error: Error creating a file:  The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined {"code":"ERR_INVALID_ARG_TYPE","stack":"TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined\n    at new NodeError (node:internal/errors:393:5)\n    at Function.from (node:buffer:328:9)\n    at createHandler (/api/node_modules/parse-server/src/Routers/FilesRouter.js:167:35)\n    at processTicksAndRejections (node:internal/process/task_queues:95:5)"}
error: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined {"code":130,"stack":"TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined\n    at new NodeError (node:internal/errors:393:5)\n    at Function.from (node:buffer:328:9)\n    at createHandler (/api/node_modules/parse-server/src/Routers/FilesRouter.js:167:35)\n    at processTicksAndRejections (node:internal/process/task_queues:95:5)"}
`


Pull request description:
fix: File upload fails when uploading base64 data
### New Pull Request Checklist
- [x] I am not disclosing a [vulnerability](https://github.com/parse-community/parse-server/blob/master/SECURITY.md).
- [x] I am creating this PR in reference to an [issue](https://github.com/parse-community/Parse-SDK-JS/issues?q=is%3Aissue).

### Issue Description
Parse server throws the following error while uploading files:
`The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined`

Related issue: #1579
Closes #1579

### Approach
Add in the missing assignment of `_data` property with the uploaded file data in `ParseFile.js`

### TODOs before merging
- [x] Add tests
- [x] Remove overriding of Parse server in **package.json**
- [x] Rollback the content of **package-lock.json**
- [ ] Add entry to changelog
- [ ] Add changes to documentation (guides, repository pages, in-code descriptions)x

Patch:
diff --git a/src/ParseFile.js b/src/ParseFile.js
index 197d8c0..08349f9 100644
--- a/src/ParseFile.js
+++ b/src/ParseFile.js
@@ -137,7 +137,10 @@ class ParseFile {
       } else if (data && typeof data.base64 === 'string') {
         const base64 = data.base64.split(',').slice(-1)[0];
         const dataType =
-          specifiedType || data.base64.split(';').slice(0, 1)[0].split(':').slice(1, 2)[0] || 'text/plain';
+          specifiedType ||
+          data.base64.split(';').slice(0, 1)[0].split(':').slice(1, 2)[0] ||
+          'text/plain';
+        this._data = base64;
         this._source = {
           format: 'base64',
           base64,


Test patch:
diff --git a/integration/test/ParseFileTest.js b/integration/test/ParseFileTest.js
index c594ff3..d8e68c3 100644
--- a/integration/test/ParseFileTest.js
+++ b/integration/test/ParseFileTest.js
@@ -64,7 +64,7 @@ describe('Parse.File', () => {
     assert.equal(data, 'ParseA==');
   });
 
-  it('can get file data from base64', async () => {
+  it('can get file data from base64 (saved)', async () => {
     const file = new Parse.File('parse-server-logo', { base64: 'ParseA==' });
     await file.save();
     let data = await file.getData();
@@ -76,6 +76,12 @@ describe('Parse.File', () => {
     assert.equal(data, 'ParseA==');
   });
 
+  it('can get file data from base64 (unsaved)', async () => {
+    const file = new Parse.File('parse-server-logo', { base64: 'ParseA==' });
+    const data = await file.getData();
+    assert.equal(data, 'ParseA==');
+  });
+
   it('can get file data from full base64', async () => {
     const file = new Parse.File('parse-server-logo', {
       base64: 'data:image/jpeg;base64,ParseA==',
diff --git a/src/__tests__/ParseFile-test.js b/src/__tests__/ParseFile-test.js
index caab362..2e66ba1 100644
--- a/src/__tests__/ParseFile-test.js
+++ b/src/__tests__/ParseFile-test.js
@@ -64,18 +64,21 @@ describe('ParseFile', () => {
     const file = new ParseFile('parse.txt', { base64: 'YWJj' });
     expect(file._source.base64).toBe('YWJj');
     expect(file._source.type).toBe('text/plain');
+    expect(file._data).toBe('YWJj');
   });
 
   it('can create files with base64 encoding (1 padding)', () => {
     const file = new ParseFile('parse.txt', { base64: 'YWI=' });
     expect(file._source.base64).toBe('YWI=');
     expect(file._source.type).toBe('text/plain');
+    expect(file._data).toBe('YWI=');
   });
 
   it('can create files with base64 encoding (2 padding)', () => {
     const file = new ParseFile('parse.txt', { base64: 'ParseA==' });
     expect(file._source.base64).toBe('ParseA==');
     expect(file._source.type).toBe('text/plain');
+    expect(file._data).toBe('ParseA==');
   });
 
   it('can set the default type to be text/plain when using base64', () => {
@@ -84,6 +87,7 @@ describe('ParseFile', () => {
     });
     expect(file._source.base64).toBe('ParseA==');
     expect(file._source.type).toBe('text/plain');
+    expect(file._data).toBe('ParseA==');
   });
 
   it('can extract data type from base64', () => {
@@ -92,6 +96,7 @@ describe('ParseFile', () => {
     });
     expect(file._source.base64).toBe('ParseA==');
     expect(file._source.type).toBe('image/png');
+    expect(file._data).toBe('ParseA==');
   });
 
   it('can extract data type from base64 with a filename parameter', () => {
@@ -100,6 +105,7 @@ describe('ParseFile', () => {
     });
     expect(file._source.base64).toBe('ParseA==');
     expect(file._source.type).toBe('application/pdf');
+    expect(file._data).toBe('ParseA==');
   });
 
   it('can create files with file uri', () => {
@@ -116,6 +122,7 @@ describe('ParseFile', () => {
     });
     expect(file._source.base64).toBe('ParseA==');
     expect(file._source.type).toBe('audio/m4a');
+    expect(file._data).toBe('ParseA==');
   });
 
   it('can extract data type from base64 with a complex mime type', () => {
@@ -124,6 +131,7 @@ describe('ParseFile', () => {
     });
     expect(file._source.base64).toBe('ParseA==');
     expect(file._source.type).toBe('application/vnd.google-earth.kml+xml');
+    expect(file._data).toBe('ParseA==');
   });
 
   it('can extract data type from base64 with a charset param', () => {
@@ -132,18 +140,21 @@ describe('ParseFile', () => {
     });
     expect(file._source.base64).toBe('ParseA==');
     expect(file._source.type).toBe('application/vnd.3gpp.pic-bw-var');
+    expect(file._data).toBe('ParseA==');
   });
 
   it('can create files with byte arrays', () => {
     const file = new ParseFile('parse.txt', [61, 170, 236, 120]);
     expect(file._source.base64).toBe('ParseA==');
     expect(file._source.type).toBe('');
+    expect(file._data).toBe('ParseA==');
   });
 
   it('can create files with all types of characters', () => {
     const file = new ParseFile('parse.txt', [11, 239, 191, 215, 80, 52]);
     expect(file._source.base64).toBe('C++/11A0');
     expect(file._source.type).toBe('');
+    expect(file._data).toBe('C++/11A0');
   });
 
   it('can create an empty file', () => {
@@ -341,6 +352,7 @@ describe('ParseFile', () => {
     const file = new ParseFile('parse.txt', [61, 170, 236, 120], '', metadata, tags);
     expect(file._source.base64).toBe('ParseA==');
     expect(file._source.type).toBe('');
+    expect(file._data).toBe('ParseA==');
     expect(file.metadata()).toBe(metadata);
     expect(file.tags()).toBe(tags);
   });