3

I'm trying to upload an image to google drive. I followed this tutorial for uploading files, I can upload a simple file but when I try to upload an image I get a corrupted image. In fact, the file is uploaded but the image I want to see is corrupted. I know that the problem is in the body of the request but I don't know where because it's a strange way (boundaries, delimiters, etc). Everything works fine except for the content of the image so you just need to look at the post method that uploads the image. This is my code:

import { Upload } from 'antd';
import React, { Component } from 'react';

var SCOPE = 'https://www.googleapis.com/auth/drive.file';
var discoveryUrl = 'https://www.googleapis.com/discovery/v1/apis/drive/v3/rest';


class App extends Component {
  state = {
    name: '',
    googleAuth: '',
    body: ''
  }
  componentDidMount(){
    var script = document.createElement('script');
    script.onload=this.handleClientLoad;
    script.src="https://apis.google.com/js/api.js";
    document.body.appendChild(script);
  }


  initClient = () => {
    try{
      window.gapi.client.init({
          'apiKey': "apikey",
          'clientId': "clientid",
          'scope': SCOPE,
          'discoveryDocs': [discoveryUrl]
        }).then(() => {
          this.setState({
            googleAuth: window.gapi.auth2.getAuthInstance()
          })
          this.state.googleAuth.isSignedIn.listen(this.updateSigninStatus);  
         document.getElementById('signin-btn').addEventListener('click', this.signInFunction);
         document.getElementById('signout-btn').addEventListener('click', this.signOutFunction);

      });
    }catch(e){
      console.log(e);
    }
  }


  signInFunction =()=>{
    console.log(this.state.googleAuth)
    this.state.googleAuth.signIn();
    console.log(this.state.googleAuth)
    this.updateSigninStatus()
  }

  signOutFunction =()=>{
    this.state.googleAuth.signOut();
    this.updateSigninStatus()
  }

  updateSigninStatus = ()=> {
    this.setSigninStatus();
  }

  setSigninStatus= async ()=>{
    console.log(this.state.googleAuth.currentUser.get())
    var user = this.state.googleAuth.currentUser.get();
    console.log(user)
    if (user.wc == null){
      this.setState({
        name: ''
      });
    }
    else{
      var isAuthorized = user.hasGrantedScopes(SCOPE);
      if(isAuthorized){
        console.log('USER')
        console.log(user)
        this.setState({
          name: user.vt.Ad
        });

        const boundary = '-------314159265358979323846';
        const delimiter = "\r\n--" + boundary + "\r\n";
        const close_delim = "\r\n--" + boundary + "--";
        var fileName='mychat123.png';
        var contentType='image/png'
        var metadata = {
          'name': fileName,
          'mimeType': contentType
        };

          var multipartRequestBody =
            delimiter +  'Content-Type: application/json\r\n\r\n' +
            JSON.stringify(metadata) +
            delimiter +
            'Content-Type: ' + contentType + '\r\n';

            multipartRequestBody +=  + '\r\n' + this.state.body;
            multipartRequestBody += close_delim;

          console.log(multipartRequestBody);
          var request = window.gapi.client.request({
            'path': 'https://www.googleapis.com/upload/drive/v3/files',
            'method': 'POST',
            'params': {'uploadType': 'multipart'},
            'headers': {
              'Content-Type': contentType
            },
            'body': multipartRequestBody
          });

        request.execute(function(file) {
          console.log(file)
        });
      }
    }
  }

  getBase64(file) {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.readAsDataURL(file);
      reader.onload = () => resolve(reader.result);
      reader.onerror = error => reject(error);
    });
  }
  

  handleChange = async file => {
    let image

    if (file.currentTarget) {
      image = file.currentTarget.currentSrc;
      console.log(file.currentTarget)
    } else {
      if (!file.file.url && !file.file.preview) {
        file.file.preview = await this.getBase64(file.file.originFileObj);
      }
      image = file.file.preview;
      console.log(file.file)
      this.setState({
        body: file.file.preview
      });
    }

    console.log(image)
    
  }

  handleClientLoad = ()=>{
    window.gapi.load('client:auth2', this.initClient);
  }

  render() {
    return (
      <div className="App">
        <Upload 
          style={{width: '100%', height: '200px' }} 
          listType="picture-card" 
          onChange={this.handleChange} >
          <div>
            <div style={{ marginTop: 8 }}>Subir imagen</div>
          </div>
        </Upload>
        <div>UserName: <strong>{ this.state.name}</strong></div>
        <button id="signin-btn">Sign In</button>
        <button id="signout-btn">Sign Out</button>
      </div>
    );
  }
}

export default App;

Why is my file corrupted? It says that this is not a PNG file. What's the correct way to upload an image to drive? Thank you!!

Xim123
  • 145
  • 3
  • 21

2 Answers2

4

Modification points:

  • From your script, it seems that this.state.body is the base64 data. In this case, it is required to add Content-Transfer-Encoding: base64 to the header of data in the request body.
    • And please be careful the line breaks of the request body for multipart/form-data.
  • When you want to use uploadType=multipart, please set multipart/form-data; boundary=### to the header as the content type. In your script, it seems that the content type is image/png.

When above points are reflected to your script, it becomes as follows.

Modified script:

From:
const boundary = '-------314159265358979323846';
const delimiter = "\r\n--" + boundary + "\r\n";
const close_delim = "\r\n--" + boundary + "--";
var fileName='mychat123.png';
var contentType='image/png'
var metadata = {
  'name': fileName,
  'mimeType': contentType
};

  var multipartRequestBody =
    delimiter +  'Content-Type: application/json\r\n\r\n' +
    JSON.stringify(metadata) +
    delimiter +
    'Content-Type: ' + contentType + '\r\n';

    multipartRequestBody +=  + '\r\n' + this.state.body;
    multipartRequestBody += close_delim;

  console.log(multipartRequestBody);
  var request = window.gapi.client.request({
    'path': 'https://www.googleapis.com/upload/drive/v3/files',
    'method': 'POST',
    'params': {'uploadType': 'multipart'},
    'headers': {
      'Content-Type': contentType
    },
    'body': multipartRequestBody
  });
To:
const boundary = '-------314159265358979323846';
const delimiter = "--" + boundary + "\r\n";
const close_delim = "\r\n--" + boundary + "--";
var fileName = 'mychat123.png';
var contentType = 'image/png';
var metadata = {'name': fileName,'mimeType': contentType};
var multipartRequestBody = delimiter +
'Content-Type: application/json\r\n\r\n' +
JSON.stringify(metadata) + "\r\n" +
delimiter +
'Content-Type: ' + contentType + '\r\n' +
'Content-Transfer-Encoding: base64\r\n\r\n' +
this.state.body +
close_delim;
console.log(multipartRequestBody);
var request = window.gapi.client.request({
  'path': 'https://www.googleapis.com/upload/drive/v3/files',
  'method': 'POST',
  'params': {'uploadType': 'multipart'},
  'headers': {'Content-Type': 'multipart/form-data; boundary=' + boundary},
  'body': multipartRequestBody
});
  • In the upload of Drive API, in the current stage, it seems that both multipart/form-data and multipart/related can be used.

Note:

  • In this answer, it supposes that your access token can be used for uploading the file to Google Drive. Please be careful this.

Reference:

Added:

From your replying, I noticed that you wanted to use fetch instead of gapi.client.request. In this case, the sample script is as follows.

This is your script in your comment.

const fd = new FormData();
fd.append("file", this.state.body);
fd.append("title", 'test.png');
const options = {
  method: 'POST',
  headers: { Authorization: "Bearer" + " " + window.gapi.auth.getToken().access_token },
  body: fd
};
await fetch("googleapis.com/upload/drive/v3/files", options)
.then(response => response.json())
.then(jsonResp => { console.log(jsonResp) });

Modification points:

  • At Drive API v3, the property for setting the filename is name.
  • uploadType=multipart is required to be used in the endpoint.
  • The base64 data is required to be converted to the blob.

When above points are reflected to your script, it becomes as follows.

Sample script:

// Reference: https://stackoverflow.com/a/16245768
// This method converts from base64 data to blob.
const b64toBlob = (b64Data, contentType='', sliceSize=512) => {
  const byteCharacters = atob(b64Data);
  const byteArrays = [];

  for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    const slice = byteCharacters.slice(offset, offset + sliceSize);

    const byteNumbers = new Array(slice.length);
    for (let i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }

    const byteArray = new Uint8Array(byteNumbers);
    byteArrays.push(byteArray);
  }

  const blob = new Blob(byteArrays, {type: contentType});
  return blob;
}

const metadata = {name: 'test.png'};
const fd = new FormData();
fd.append('metadata', new Blob([JSON.stringify(metadata)], {type: 'application/json'}));
fd.append('file', b64toBlob(this.state.body, "image/png"));
fetch('https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart', {
  method: 'POST',
  headers: {Authorization: 'Bearer ' + window.gapi.auth.getToken().access_token},
  body: fd
})
.then(response => response.json())
.then(jsonResp => { console.log(jsonResp) });
Tanaike
  • 181,128
  • 11
  • 97
  • 165
  • with that I receive a bad request response, I will try to make this request with an http post. In fact, I've done this and the image is being created. My problem is that I can't set the name of the image. – Xim123 Nov 10 '20 at 08:00
  • const fd = new FormData(); fd.append("file", this.state.body); fd.append("title", 'test.png'); const options = { method: 'POST', headers: { Authorization: "Bearer" + " " + window.gapi.auth.getToken().access_token }, body: fd, }; await fetch("https://www.googleapis.com/upload/drive/v3/files", options) .then(response => response.json()) .then(jsonResp => { console.log(jsonResp) }); – Xim123 Nov 10 '20 at 08:01
  • @Xim123 Thank you for replying. I apologize for the inconvenience. When I saw the script of your replying, from your question, I couldn't notice that you wanted to use `fetch` instead of `gapi.client.request`. This is due to my poor English skill. I deeply apologize for this. About the sample script for using `fetch`, I added one more sample script as an answer. Could you please confirm it? If that was not useful for your situation, I apologize again. – Tanaike Nov 10 '20 at 08:36
  • Yes thank you, you are the best!! it works without using the methodb64toBlob. Thanks a lot !!!@Tanaike – Xim123 Nov 10 '20 at 16:09
  • @Xim123 Thank you for replying. I'm glad your issue was resolved. Thank you, too. – Tanaike Nov 10 '20 at 21:58
  • 1
    @Tanaike thank you, thank you! The only thing that worked was using FormData instead of the multipart/related as Google documents and all other answers on SO mention but doesnt work for images, etc. I kept getting a corrupt file in Drive. With your FormData example it worked without a hitch! – poeticGeek Nov 13 '20 at 10:33
  • @poeticGeek Thank you for your comment and testing it. – Tanaike Nov 13 '20 at 12:13
0

I created a customization on top of react-uploady, called drive-uploady that makes it trivial to upload to google drive with all the power of Uploady: hooks, UI components, progress, etc.

for example:

import React from "react";
import DriveUploady from "drive-uploady";
import UploadButton from "@rpldy/upload-button";

export const App = () => {

    return <DriveUploady        
            clientId="<my-client-id>"
            scope="https://www.googleapis.com/auth/drive.file"
           >                
            <UploadButton>Upload to Drive</UploadButton>
        </DriveUploady>;
};

Is all you need to render an upload button that logs in the user to Drive and uploads.

poeticGeek
  • 1,001
  • 10
  • 14