Strange CORS issue with Google Drive API - javascript

Google Drive API, files.update:
https://developers.google.com/drive/api/v3/reference/files/update
This API is giving CORS error, the strange thing is in the same drive app that I'm creating, on the same domain (I'm testing it directly online):
files.create API runs OK
files.update API gives me CORS error (preflight request by browser can't find allow origin header in response)
The documentation on developers.google.com doesn't mention about CORS issue, what could be the problem that one API is ok while the other is not?
The 2 functions to create and update are here but shouldn't be a code issue, since CORS is about the origin of script while the functions are on the same page:
// Create new file in Gdrive
// See: https://developers.google.com/drive/api/v3/reference/files/create
// See: https://tanaikech.github.io/2018/08/13/upload-files-to-google-drive-using-javascript
async function gdrive_create_file(Folder_Id,File_Name,Binobj){
var Metadata = {
"name": File_Name, // Filename at Google Drive
"mimeType": "text/plain", // mimeType at Google Drive
"parents": [Folder_Id], // Folder ID at Google Drive
};
// Here gapi is used for retrieving the access token.
var Access_Token = gapi.auth.getToken().access_token;
var Form = new FormData();
Form.append("metadata", new Blob([JSON.stringify(Metadata)], {type: 'application/json'}));
Form.append("file", Binobj);
// Make request to Gdrive
var [Lock,Unlock] = new_lock();
var File_Id = null;
var Xhr = new XMLHttpRequest();
// Gdrive to return id as indicated in 'fields=id'
Xhr.open(
"post",
"https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id"
);
Xhr.setRequestHeader("Authorization", "Bearer "+Access_Token);
Xhr.responseType = "json";
Xhr.onload = ()=>{
log("[Dad's TE] Gdrive file id:",Xhr.response.id); // Retrieve uploaded file ID.
File_Id = Xhr.response.id;
if (File_Id==null)
alert("Failed to create file in Gdrive!");
Unlock();
};
Xhr.send(Form);
// Wait to get resulting file id
await Lock;
return File_Id;
}
// Update a file in Gdrive
log("DEBUG HERE: 0");
async function gdrive_update_file(File_Id,File_Name,Binobj){
var Metadata = {
"name": File_Name, // Filename at Google Drive
"mimeType": "text/plain" // mimeType at Google Drive
};
// Here gapi is used for retrieving the access token.
var Access_Token = gapi.auth.getToken().access_token;
var Form = new FormData();
Form.append("metadata", new Blob([JSON.stringify(Metadata)], {type: 'application/json'}));
Form.append("file", Binobj);
// Make request to Gdrive
var [Lock,Unlock] = new_lock();
var Xhr = new XMLHttpRequest();
// Gdrive to return id as indicated in 'fields=id'
Xhr.open(
"patch",
`https://www.googleapis.com/upload/drive/v3/files/${File_Id}?uploadType=multipart&fields=id`
);
Xhr.setRequestHeader("Authorization", "Bearer "+Access_Token);
Xhr.responseType = "json";
Xhr.onload = ()=>{
log("[Dad's TE] Gdrive file id:",Xhr.response.id); // Retrieve uploaded file ID.
File_Id = Xhr.response.id;
if (File_Id==null)
alert("Failed to update file in Gdrive!");
Unlock();
};
alert("DEV ERROR: See CORS error in console log!");
Xhr.send(Form);
// Wait to get resulting file id
await Lock;
return File_Id;
}
The new_lock function:
function new_lock(){
var Unlock,Lock = new Promise((Res,Rej)=>{ Unlock=Res; });
return [Lock,Unlock];
}

In your script, how about modifying as follows? In my environment, when I tested your script, I confirmed the same error. In this case, when "patch" is modified to "PATCH", the error was removed.
Modified script:
From:
Xhr.open(
"patch",
`https://www.googleapis.com/upload/drive/v3/files/${File_Id}?uploadType=multipart&fields=id`
);
To:
Xhr.open(
"PATCH",
`https://www.googleapis.com/upload/drive/v3/files/${File_Id}?uploadType=multipart&fields=id`
);
or
Xhr.open(
"PATCH",
"https://www.googleapis.com/upload/drive/v3/files/" + File_Id + "?uploadType=multipart&fields=id"
);
And also, please remove alert("DEV ERROR: See CORS error in console log!");. When this is used, this is always displayed. Please be careful this.
When console.log(Xhr.response); is put in Xhr.onload = ()=>{,,,}, you can see the response from the API.
Note:
After I updated the file with "PATCH", when I tested the script with "patch", no error occurred. But, when new file is updated with the script with "patch", such error occurred again. So in this case, how about using "PATCH" instead of "patch"?
As the simple test script, how about the following script?
function gdrive_update_file(File_Id,File_Name,Binobj){
var Metadata = { "name": File_Name, "mimeType": "text/plain" };
var Access_Token = gapi.auth.getToken().access_token;
var Form = new FormData();
Form.append("metadata", new Blob([JSON.stringify(Metadata)], { type: 'application/json' }));
Form.append("file", Binobj);
var Xhr = new XMLHttpRequest();
Xhr.open(
"PATCH",
"https://www.googleapis.com/upload/drive/v3/files/" + File_Id + "?uploadType=multipart&fields=id"
);
Xhr.setRequestHeader("Authorization", "Bearer " + Access_Token);
Xhr.responseType = "json";
Xhr.onload = () => {
console.log(Xhr.response);
};
Xhr.send(Form);
}
References:
XMLHttpRequest.open()
HTTP request methods

Related

Google Drive API browser upload PDF empty

I can upload the pdf file, but it will upload as blank/empty file. I don't know what am i missing from here.
Backend i receive the file, i also tried without converting to Base64 and still the same thing.
using (var sr = new StreamReader(file.OpenReadStream(), System.Text.Encoding.UTF8))
{
_fContent = await sr.ReadToEndAsync();
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(_fContent);
_fContent = System.Convert.ToBase64String(plainTextBytes);
}
Frontend i create the request.
endpoint = 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id';
method = 'POST';
_metadata = {
'name': fileName,
'mimeType': 'application/pdf',
'parents': [zzzz]
};
//blob is the data we receive in backend from _fContent variable
var file = new Blob([blob], { type: 'application/pdf' });
var accessToken = gapi.auth.getToken().access_token;
var form = new FormData();
form.append('metadata', new Blob([JSON.stringify(_metadata)], { type: 'application/json' }));
form.append('file', file);
var xhr = new XMLHttpRequest();
xhr.open(method, endpoint);
xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken);
xhr.responseType = 'json';
xhr.onload = () => {
GapiUploadCallback(xhr.response, responseReturn);
};
xhr.send(form);
This is what i receive in google drive API, an empty/blank PDF file. Note: File size is
1 MB (1,424,457 bytes)
Using javascript FileReader was the only option i could solve this. PDF's are rendering ok on Google Drive now.
var fileUpload = $("#fileUpload").get(0);
var files = fileUpload.files;
var reader = new FileReader();
reader.onload = function () {
var dataURL = reader.result;
var file = new Blob([dataURL], { type: 'application/pdf' });
var accessToken = gapi.auth.getToken().access_token; // Here gapi is used for retrieving the access token.
var form = new FormData();
form.append('metadata', new Blob([JSON.stringify(_metadata)], { type: 'application/json' }));
form.append('file', file);
var xhr = new XMLHttpRequest();
xhr.open(method, endpoint);
xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken);
xhr.responseType = 'json';
xhr.onload = () => {
GapiUploadCallback(xhr.response, responseReturn);
};
xhr.send(form);
};
reader.readAsArrayBuffer(files[0]);

setting up tinymce with cloudinary in html and javascript

I've read the readme file at https://github.com/cloudinary/cloudinary_tinymce but still can't understand how to do it. Plus they do it on Ruby on Rails, which doesn't really help.
Do I really need to do server-side endpoint? It only asks for a signed URL. But I don't need it to be signed. How do I do it within JavaScript and HTML alone? I don't want to do anything inside my server except to render templates.
edit: I tried it with image_upload handler and it uploads to my Cloudinary account. But it won't give me the url for the image on successful upload (I expect to get something like https://res.cloudinary.com/strova/image/upload/v1527068409/asl_gjpmhn.jpg). Here's my code:
images_upload_handler: function (blobInfo, success, failure) {
var xhr, formData;
xhr = new XMLHttpRequest();
xhr.withCredentials = false;
xhr.open('POST', 'https://api.cloudinary.com/v1_1/strova/upload');
xhr.onload = function () {
var json;
if (xhr.status !== 200) {
failure('HTTP Error: ' + xhr.status);
return;
}
json = JSON.parse(xhr.responseText);
success(json.location);
};
formData = new FormData();
formData.append('file', blobInfo.blob(), blobInfo.filename());
formData.append('upload_preset', cloudinary_upload_preset);
xhr.send(formData);
}
Try "faking" a POST request for one. I am still trying. To figure out why the documentation "requires" a POST request. The PHP example: https://www.tinymce.com/docs-3x//TinyMCE3x#Installation/ just echos back what gets POSTED to server. The plugin must be interpolated the posted content.
Inspired by your code, I was able to resolve this pain point for myself. The missing part was that after parsing the response, the secure_url from the response needed to be called and assigned to json in the format required by TinyMCE. Following is the code:
images_upload_handler: function (blobInfo, success, failure) {
var xhr, formData;
xhr = new XMLHttpRequest();
xhr.withCredentials = false;
//restricted it to image only using resource_type = image in url, you can set it to auto for all types
xhr.open('POST', 'https://api.cloudinary.com/v1_1/<yourcloudname>/image/upload');
xhr.onload = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
var response = JSON.parse(xhr.responseText);
var url = response.secure_url; //get the url
var json = {location: url}; //set it in the format tinyMCE wants it
success(json.location);
}
};
formData = new FormData();
formData.append('file', blobInfo.blob(), blobInfo.filename());
formData.append('upload_preset', '<YourUnsignedUploadPreset>');
xhr.send(formData);
}

Dropbox direct upload files from browser

I am trying to upload files directly to dropbox [from a browser / web application], The "uploadFile" function on the code API needs the file to be uploaded available on the server, this puts me in trouble, because I do not want any files to be uploaded to my server and from there to dropbox.
$f = fopen("test.jpg", "rb"); // requires file on server
$result = $dbxClient->uploadFile("test.jpg", dbx\WriteMode::add(), $f);
fclose($f);
Tried out this https://github.com/dropbox/dropbox-js disappointed to say that there is no clear documentation, many of the links on the documentation part is broken.
I need the files to be uploaded to my account and the clients need not login to dropbox.
Any pointers would be really appreciated. looking for Ajax / JavaScript methods.
Update
I have tried the following, but no response from Dropbox
HTML
<input type="file" name="file" id="file" onchange="doUpload(event)">
JavaScript
var doUpload = function(event){
var input = event.target;
var reader = new FileReader();
reader.onload = function(){
var arrayBuffer = reader.result;
$.ajax({
url: "https://api-content.dropbox.com/1/files_put/auto/uploads/" + input.files[0].name,
headers: {
Authorization: 'Bearer ' + MyAccessToken,
contentLength: file.size
},
crossDomain: true,
crossOrigin: true,
type: 'PUT',
contentType: input.files[0].type,
data: arrayBuffer,
dataType: 'json',
processData: false,
success : function(result) {
$('#uploadResults').html(result);
}
});
}
reader.readAsArrayBuffer(input.files[0]);
}
Dropbox just posted a blog with instructions on how to do this. You can find it at https://blogs.dropbox.com/developers/2016/03/how-formio-uses-dropbox-as-a-file-backend-for-javascript-apps/ (Full disclosure, I wrote the blog post.)
Here is how to upload a file.
/**
* Two variables should already be set.
* dropboxToken = OAuth token received then signing in with OAuth.
* file = file object selected in the file widget.
*/
var xhr = new XMLHttpRequest();
xhr.upload.onprogress = function(evt) {
var percentComplete = parseInt(100.0 * evt.loaded / evt.total);
// Upload in progress. Do something here with the percent complete.
};
xhr.onload = function() {
if (xhr.status === 200) {
var fileInfo = JSON.parse(xhr.response);
// Upload succeeded. Do something here with the file info.
}
else {
var errorMessage = xhr.response || 'Unable to upload file';
// Upload failed. Do something here with the error.
}
};
xhr.open('POST', 'https://content.dropboxapi.com/2/files/upload');
xhr.setRequestHeader('Authorization', 'Bearer ' + dropboxToken);
xhr.setRequestHeader('Content-Type', 'application/octet-stream');
xhr.setRequestHeader('Dropbox-API-Arg', JSON.stringify({
path: '/' + file.name,
mode: 'add',
autorename: true,
mute: false
}));
xhr.send(file);
Then to download a file from dropbox do this.
var downloadFile = function(evt, file) {
evt.preventDefault();
var xhr = new XMLHttpRequest();
xhr.responseType = 'arraybuffer';
xhr.onload = function() {
if (xhr.status === 200) {
var blob = new Blob([xhr.response], {type: ’application/octet-stream’});
FileSaver.saveAs(blob, file.name, true);
}
else {
var errorMessage = xhr.response || 'Unable to download file';
// Upload failed. Do something here with the error.
}
};
xhr.open('POST', 'https://content.dropboxapi.com/2/files/download');
xhr.setRequestHeader('Authorization', 'Bearer ' + dropboxToken);
xhr.setRequestHeader('Dropbox-API-Arg', JSON.stringify({
path: file.path_lower
}));
xhr.send();
}
FileSaver and Blob will not work on older browsers so you could add a workaround to them.
As other answers have noted, each session uploading or downloading the file will need to have access to a dropbox token. Sending someone else's token to a user is a security issue since having the token will give them complete control over the dropbox account. The only way to make this work is to have each person authenticate with Dropbox and get their own token.
At Form.io we've implemented both the authentication and the upload/download into our platform. This makes it really easy to build web apps with dropbox as a backend for files.
"I need the files to be uploaded to my account and the clients need not login to dropbox."
Then you'll really need to do the upload server-side. To do it client side would mean sending the access token to the browser, at which point any user of your app could use that access token to do whatever they wanted with your account. (E.g. delete all the other files, upload their private DVD collection, etc.)
For security reasons, I would strongly recommend doing the upload server-side where you can keep the access token a secret.
The answers given so far don't utilize the Dropbox javascript SDK which I think would prob be the best way to go about it. Check out this link here:
https://github.com/dropbox/dropbox-sdk-js/blob/master/examples/javascript/upload/index.html
which provides an example which is ofc dependent on having downloaded the SDK. (Edit: after playing with SDK I realize that it creates a POST request similar to the accepted answer in this thread. However something the popular answer omits is the presence of an OPTIONS preflight call that the sdk makes prior to the actual POST)
I might also add that something that is not shown in the dropbox sdk examples is that you can upload a blob object to dropbox; this is useful for instance if you want to dynamically extract images from a canvas and upload them and don't want to upload something that has been selected from the file system via the file uploaded input.
Here is a brief example of the scenario I'm describing:
//code below after having included dropbox-sdk-js in your project.
//Dropbox is in scope!
var dbx = new Dropbox.Dropbox({ accessToken: ACCESS_TOKEN });
//numerous stack overflow examples on creating a blob from data uri
var blob = dataURIToBlob(canvas.toDataUrl());
//the path here is the path of the file as it will exist on dropbox.
//should be unique or you will get a 4xx error
dbx.filesUpload({path: `unq_filename.png`, contents: blob})
Many thanks to #smarx with his pointers I was able to reach the final solution.
Also I have added a few extra features like listening to upload progress so that the users can be showed with the upload progress percentage.
HTML
<input type="file" name="file" id="file" onchange="doUpload(event)">
JavaScript
var doUpload = function(event){
var input = event.target;
var reader = new FileReader();
reader.onload = function(){
var arrayBuffer = reader.result;
var arrayBufferView = new Uint8Array( arrayBuffer );
var blob = new Blob( [ arrayBufferView ], { type: input.files[0].type } );
var urlCreator = window.URL || window.webkitURL;
var imageUrl = urlCreator.createObjectURL( blob );
$.ajax({
url: "https://api-content.dropbox.com/1/files_put/auto/YourDirectory/" + input.files[0].name,
headers: {
'Authorization':'Bearer ' +YourToken,
'Content-Length':input.files[0].size
},
crossDomain: true,
crossOrigin: true,
type: 'PUT',
contentType: input.files[0].type,
data: arrayBuffer,
dataType: 'json',
processData: false,
xhr: function()
{
var xhr = new window.XMLHttpRequest();
//Upload progress, litsens to the upload progress
//and get the upload status
xhr.upload.addEventListener("progress", function(evt){
if (evt.lengthComputable) {
var percentComplete = parseInt( parseFloat(evt.loaded / evt.total) * 100);
//Do something with upload progress
$('#uploadProgress').html(percentComplete);
$('#uploadProgressBar').css('width',percentComplete+'%');
}
}, false);
},
beforeSend: function(){
// Things you do before sending the file
// like showing the loader GIF
},
success : function(result) {
// Display the results from dropbox after upload
// Other stuff on complete
},
});
}
reader.readAsArrayBuffer(input.files[0]);
}
U have used the PUT method as our only objective is to upload files,As per my studies on various resources ( StackOverflow and zacharyvoase ) A put method can stream large files, also its desigend to put files on a specified URI , if file exist the file must be replaced. A PUT method cannot be moved to a different URL other than the URL Specified.
The Risk
You are at risk by using access token at client side, there needs to be high security measures to mask the token. But modern Web dev tools like Browser consoles , Firebug etc can monitor your server requests and can see your access token.
upload.html
Upload
upload.js
$('#form_wizard_1 .button-submit').click(function () {
var ACCESS_TOKEN ="Your token get from dropbox";
var dbx = new Dropbox({ accessToken: ACCESS_TOKEN });
var fileInput = document.getElementById('files1');
var file = fileInput.files[0];
res=dbx.filesUpload({path: '/' + file.name, contents: file})
.then(function(response) {
var results = document.getElementById('results');
results.appendChild(document.createTextNode('File uploaded!'));
res=response;
console.log(response);
})
.catch(function(error) {
console.error(error);
});
}

Google Storage resumable upload range header

I'm uploading files to a GCS bucket with the JSON API and the upload is resumable. I'm doing this from the browser using JS.
The files upload with no problems. If there is a network problem then I try to resume it. Per the documentation I'm sending an empty PUT request to the upload URI. Then parsing the range header of the response. But then I get the error:
Refused to get unsafe header "range"
This is the code:
var xhr = new XMLHttpRequest();
xhr.open("PUT", url, true);
xhr.setRequestHeader("Authorization", token);
xhr.setRequestHeader("content-range", "bytes */" + file_object.size);
xhr.onreadystatechange = function () {
if (xhr.readyState != 4) {
return;
}
if (xhr.status === 308) {
var range = xhr.getResponseHeader("range"); // Error here
// continue with the upload
}
};
xhr.send();
I'm creating the upload URI at the server side with Python. These are the headers included in the request:
headers = {
"Authorization": authorization,
"Content-Type": "application/json; charset=UTF-8",
"origin": 'http://' + self.request.headers.get('HOST'),
"X-Upload-Content-Type": content_type,
"X-Upload-Content-Length": content_length,
}
How can I read the header range?

jQuery.ajax get image from wcf service and display

I'm trying to download and display an image that is returned from a wcf service using jQuery.ajax. I'm not able to work with the data I've received and I'm not sure why. I've tried a lot of things but nothing seems to work.
Here the service :
public Stream DownloadFile(string fileName, string pseudoFileName)
{
string filePath = Path.Combine(PictureFolderPath, fileName);
if (System.IO.File.Exists(filePath))
{
FileStream resultStream = System.IO.File.OpenRead(filePath);
WebOperationContext.Current.OutgoingResponse.ContentType = "application/x-www-form-urlencoded";
return resultStream;
}
else
{
throw new WebFaultException(HttpStatusCode.NotFound);
}
}
Here my ajax call :
$.ajax({
type: "GET",
url: apiURL + serviceDownloadFile.replace('{filename}', filename),
headers: headers,
contentType: "application/x-www-form-urlencoded",
processData : false,
success: function(response) {
var html = '<img src="data:image/jpeg;base64,' + base64encode(response) +'">';
$("#activitiesContainer").html(html);
},
error: function (msg) {
console.log("error");
console.log(msg);
}
});
Putting the url in a <img> tag does display the image properly, but since the service requires an authorization header, the page ask me for credentials each time.
So my question is, what to do this the response data so I can display it ? using btoa(); on the response displays an error :
string contains an invalid character
Thanks.
As suggested by Musa, using XMLHttpRequest directly did the trick.
var xhr = new XMLHttpRequest();
xhr.open('GET', apiURL + serviceDownloadFile.replace('{filename}', filename).replace('{pseudofilename}', fileNameExt), true);
xhr.responseType = 'blob';
xhr.setRequestHeader("authorization","xxxxx");
xhr.onload = function(e) {
if (this.status == 200) {
var blob = this.response;
var img = document.createElement('img');
img.onload = function(e) {
window.URL.revokeObjectURL(img.src); // Clean up after yourself.
};
img.src = window.URL.createObjectURL(blob);
document.body.appendChild(img);
}
};
xhr.send();

Categories