Corrupt files when uploading from Node - javascript

I'm converting powerpoint files to individual images, and the images that are produced end up corrupted when I upload from Node.
The npm modules I'm using are request and node-form-data.
I upload a file from my Node app like this:
var request = require('request');
var FormData = require('form-data');
function processPresentation(fileName,fileLoc,userId,customerId){
var data = new FormData();
data.append('my_file',fs.createReadStream(fileLoc));
var options = {
url: config.getConverter()+"?tenant="+customerId+"&author="+userId+"&name="+fileName+"&ext=ppt",
method: 'POST',
form:data,
headers:{'x-auth-token':token,'Content-Type':'application/vnd.openxmlformats-officedocument.presentationml.presentation'}
};
request.post(options,function(error,response,body){
console.log(body);
if(error){
console.log('error',error);
}
if(!response){
}
if(response.statusCode === 200){
addPresentation(JSON.parse(body),userId);
}
});
}
And it goes through my conversion process, I get a file like this as output:
Here's what all this actually says, if you open the powerpoint file and look at the text:
http://pastebin.com/Dbh0JPKA
When I use Postman and upload the same file like this:
POST HTTP/1.1
Host: xxxx.xxxxxxxxxx.net?name=00a94ec9-8f70-4279-8972-f49935cda295&ext=ppt&tenant=543840f80019abda4937a9e2&author=543bef549f8d54a53a02f6d9
Content-Type: application/vnd.openxmlformats-officedocument.presentationml.presentation
x-auth-token: 772a5c0c023a447f68a9ac4fb2bb4bd39bafeb16b753df2222ffc835750cbbe6a4ef9ee82fab0902f39bc26851016a873d44c91a64f67de5e10044ef0787cebe
Cache-Control: no-cache
Postman-Token: 74aff430-f6b8-c7cd-d477-b9cc35897bb7
undefined
I get output like this:
Which is what I want.

Ok, I think that the problem is that you are sending mime/multipart encoded data and you just want to send the file as the raw body of the post. There are a couple ways to do this.
Option #1, Stream the File:
var options = {
url: config.getConverter()+"?tenant="+customerId+"&author="+userId+"&name="+fileName+"&ext=ppt",
headers:{'x-auth-token':token,'Content-Type':'application/vnd.openxmlformats-officedocument.presentationml.presentation'}
};
fs.createReadStream(fileLoc).pipe(request.post(options));
This technique is actually streaming the file as the raw post body for your request.
Option #2, Read the File then Post:
var options = {
url: config.getConverter()+"?tenant="+customerId+"&author="+userId+"&name="+fileName+"&ext=ppt",
headers:{'x-auth-token':token,'Content-Type':'application/vnd.openxmlformats-officedocument.presentationml.presentation'},
body: fs.readFileSync(fileLoc)
};
request.post(options, callback);
Here you are reading the file into a string and posting it using the body option. This sets the post body raw rather than using a mime/multipart encoding as you get with formData.

Try dropping the require for form-data and just do this:
var options = {
url: config.getConverter()+"?tenant="+customerId+"&author="+userId+"&name="+fileName+"&ext=ppt",
method: 'POST',
formData: {
my_file: fs.createReadStream(fileLoc)
},
headers:{'x-auth-token':token,'Content-Type':'application/vnd.openxmlformats-officedocument.presentationml.presentation'}
};
Important there is formData vs form

Doing this fixed it:
var length = fs.statSync(fileLoc).size;
console.log('length is',length);
var req = request.post({uri: config.getConverter()+"?tenant="+customerId+"&author="+userId+"&name="+fileName+"&ext=ppt",
headers:{
'x-auth-token':token,
'Content-Type':'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'content-length':length
}
});
fs.createReadStream(fileLoc).pipe(req).pipe(process.stdout);

Related

Send canvas.toDataURL images to nodejs

I'm trying to send image from front-end script to my server.
Front-end script:
var img_data = canvas.toDataURL('image/jpg'); // contains screenshot image
// Insert here POST request to send image to server
And I'm trying to accept the data in the backend and store it into req.files to be able to access like this:
const get_image = (req, res) => {
const File = req.files.File.tempFilePath;
}
What way can I do to send the image to the server and get the image like in the example above?
your img_data is a base 64 string, which you can send to server directly in a post request
e.g.
await fetch('/api/path', { method: 'POST', headers: { "content-type": "application/json"}, body: JSON.stringify({ file: img_data }) });
On your backend, you can convert this string to binary, and save to file.
var fs = require('fs');
app.post('/api/path', async (req, res) => {
const img = req.body.file;
var regex = /^data:.+\/(.+);base64,(.*)$/;
var matches = string.match(regex);
var ext = matches[1];
var data = matches[2];
var buffer = Buffer.from(data, 'base64'); //file buffer
.... //do whatever you want with the buffer
fs.writeFileSync('imagename.' + ext, buffer); //if you do not need to save to file, you can skip this step.
....// return res to client
})
You have to convert it to a Blob first, and then append it to a Form. The form would be the body of the request that you send to server.
canvas.toBlob(function(blob){
var form = new FormData(),
request = new XMLHttpRequest();
form.append("image", blob, "filename.png");
request.open("POST", "/upload", true);
request.send(form);
}, "image/png");

Converting dataURI to file for upload via REST does not work but normal filefield upload works fine

I have implemented a solution that accepts a single file upload (image for profile) in a Django Rest Framework backend. This route api/people/id/upload_image. Only accepts a parameter with an image. and can be used via HTTP POST.
When uploading via a fileinput field in eg. Postman, the default Django API or via browser fetch() in my Vue.js application is no problem. So it seems as long as it is a default form-upload field it is doing its job.
But in my frond-end (vuejs 3) I am using an image-cropper. Users can upload an image and via the javascript cropper the image can be cropped. This is important for the UI because I need a square image. The cropper uses HTMLCanvasElement.toDataURL() as export format.
And what seemed to be not that difficult gets me stuck for days now. I just can't find a way to convert and POST the cropped image in such a way that is accepted by the upload_image API backend. I am using Fetch() for sending this POST call.
I am not a javascript expert so I get my knowledge via internet and I tried it in several ways; with first creating a BLOB from the dataURI, and by creating a File before feeding it to dataForm and send it as :body in Fetch()
The dataURI seems OK because I am also replacing the cropped image directly in the HTML. And that looks totally fine. The API is responding with an 'HTTP 200 OK'. But the old image is not being replaced.
So my assumption is that there is something wrong with the image send to the API, because via normal fileupload everything works fine. How should I convert this dataURI in a proper way so it can be send and accepted by the API endpoint. And how should the API call look like: headers, body..
this is my last attempt in converting and sending the cropped image: (dataURIimage is OK)
uploadPhoto(context, dataURIimage) {
const blob = dataURItoBlob(dataURIimage);
const resultFile = new File([blob], "picture", {
type: "image/png"
});
const formData = new FormData();
formData.append("image", resultFile);
const headerToken = "Token" + " " + this.getters.getToken;
const url =
"https://workserver-7e6s4.ondigitalocean.app/api/people/" +
this.getters.getProfiel.id +
"/upload_image/";
fetch(url, {
method: "POST",
headers: {
'Content-Type': 'multipart/form-data',
'Authorization': headerToken,
},
body: formData,
})
.then(function(response) {
console.log(response.status)
if (response.ok) {
return response.json();
}
})
.then(function(data) {
console.log(data.image);
})
.catch(function(error) {
alert(error + " " + ":ERROR");
});
},
function dataURItoBlob(dataURI) {
// convert base64 to raw binary data held in a string
// doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this
var byteString = atob(dataURI.split(',')[1]);
// separate out the mime component
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
// write the bytes of the string to an ArrayBuffer
var ab = new ArrayBuffer(byteString.length);
var ia = new Uint8Array(ab);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
return new Blob([ab], { type: mimeString });
}
Martijn dekker

Upload File with Cypress.io via Request

Given this simple form
<form action="/upload_file" method="POST" enctype="multipart/form-data" id="upload_file_form">
<input type="file" name="file" required>
<input type="text" placeholder="Select file" disabled>
<input type="submit" value="upload">
</form>
Since native events are not supported by cypress.io (can't select a file), I have to use a post-request.
The request structure in cypress.io looks like
cy.request({
method: 'POST',
url: '/some/url',
form: true,
body: { ... },
headers:{ ... }
})
I would like to know how I could send a simple *.txt
Any suggestions appreciated!
Will leave this here in case it helps somebody
https://github.com/javieraviles/cypress-upload-file-post-form
Second scenario (send_form_data_with_file_in_post_request_spec.js):
I want to build up the FormData myself( new FormData(), formData.append/formData.set ) and send it directly with a POST request to the backend or submit the form with the FormData I have created.
For this case, the transmitted data must be in the same format as the form's submit(), which type is set to "multipart/form-data". Having a look at the MDN web docs to see how you can build a FormData: Using FormData Objects, and knowing that at this very moment (Cypress 2.1.0) cy.request doesn't support FormData (multipart/form-data) so we will need a XMLHttpRequest, the test can be performed as follows.
Include the following code in your "commands.js" file within the cypress support folder, so the command cy.form_request() can be used from any test:
// Performs an XMLHttpRequest instead of a cy.request (able to send data as
// FormData - multipart/form-data)
Cypress.Commands.add('form_request', (method, url, formData, done) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.onload = function () {
done(xhr);
};
xhr.onerror = function () {
done(xhr);
};
xhr.send(formData);
})
Then, in case you want to send the same form as before (form containing an excel file and other plain inputs) but build it by yourself and send it directly to the server, the test would be something like this:
describe('Testing the API', function () {
it('Receives valid FormData and proccesses the information correctly',
function () {
/*
The reason why this test may look a bit tricky is because the backend endpoint is expecting the
submission of a web Form (multipart/form-data), not just data within a POST. The "cy.request()"
command doesn't support sending a web Form as a body in a POST request, so the test uses a support
command that has been created to perform a genuine XMLHttpRequest where a web Form can be placed.
*/
//Declarations
const fileName = 'your_excel_file.xlsx';
const method = 'POST';
const url = 'http://localhost:3000/api/excel_form';
const fileType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
const inputContent2 = 'input_content2';
const expectedAnswer = '{"msg":"X elements from the excel where successfully imported"}';
// Get file from fixtures as binary
cy.fixture(fileName, 'binary').then( (excelBin) => {
// File in binary format gets converted to blob so it can be sent as Form data
Cypress.Blob.binaryStringToBlob(excelBin, fileType).then((blob) => {
// Build up the form
const formData = new FormData();
formData.set('file', blob, fileName); //adding a file to the form
formData.set('input2', inputContent2); //adding a plain input to the form
.
.
.
// Perform the request
cy.form_request(method, url, formData, function (response) {
expect(response.status).to.eq(200);
expect(expectedAnswer).to.eq(response.response);
});
})
})
})
})
I just needed from Cypress context to upload a file somewhere (to mock something out before the browser does request it).
The following is working just fine in my case:
cy.fixture('something.svg').then((file) => {
cy.request({
url: 'http://mock/uploads/some-id',
method: 'put',
body: file,
});
});
bahmutov's cypress-form-data-with-file-upload makes reference to the github issues (311 and 170) where this problem is being considered and provides a rather dirty workaround for those that cannot accommodate Cypress' limitation on this front.
The workaround is to create a custom XHR object and use that to submit the request with the file attached, instead of Cypress' cy.request. The provided workaround is limited to functioning with an <input type="file" /> element's value. In my case, I needed to use a fixture in my request, so my solution was to create a custom cypress command
Cypress.Commands.add("xhr", (method, url, formdata) => {
// inspired by https://github.com/bahmutov/cypress-form-data-with-file-upload/blob/8fe6106d28eef0634c78564c746746d1cc354e99/index.js
// fixes lack of FormData (multipart/form-data) support in cy.request
cy.window()
.then(win => {
return new Promise((resolve, reject) => {
const XHR = new win.XMLHttpRequest()
XHR.onload = resolve
XHR.open(method, url)
XHR.send(formdata)
})
})
})
And attach Blobs or Files as needed to an instance of FormData.
I strongly recommend accommodating Cypress, for example, by accepting base64-encoded file data as plain text, if you can, due to the complexity associated with this workaround.
I ran into this issue today and developed a similar solution to the other answers, but it allows you to use the .then() Cypress syntax.
Add this to support/commands.js:
Cypress.Commands.add("form_request", (url, formData) => {
return cy
.server()
.route("POST", url)
.as("formRequest")
.window()
.then(win => {
var xhr = new win.XMLHttpRequest();
xhr.open(method, url);
xhr.send(formData);
})
.wait("#formRequest");
});
Then you'd be able to do the following in your tests:
cy
.form_request(url, formData)
.then(response => {
// do stuff with your response
});
I spent most of the weekend on this so will post the solution that works for me.
I couldn't get the example in the NPM package cypress-upload-file-post-form to work as it is written. Specifically I got a function not found error on Cypress.Blob.binaryStringToBlob.
Step 1
In support/commands.js add this function
Cypress.Commands.add('multipartFormRequest', (method, url, formData, done) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.onload = function () {
done(xhr);
};
xhr.onerror = function () {
done(xhr);
};
xhr.send(formData);
});
Step 2
Add a file called Base64TestCV.rtf to the fixtures folder.
I wanted a .rtf file but obviously you can make a .txt file - whatever you do it must be encoded in base64 by using an online converter or the base64 command in Linux.
For testing purposes, make sure it contains the text MyCypressTestCV (encoded in base64 obviously).
Step 3
Create and run your test. In this example my .rtf file contains the text MyCypressTestCV which is reflected back by httpbin.org so it proves it was decoded from base64 correctly and uploaded.
describe('Upload a file to a multipart form using cy.request', function () {
it('Performs a multipart post to httpbin.org', function () {
const baseUrl = 'http://httpbin.org';
const postUrl = `${baseUrl}/post`;
cy.visit(baseUrl); // Cypress will be very buggy if you don't do at least one cy.visit
cy.request(baseUrl).as('multipartForm'); // pretend we are doing the GET request for the multipart form
const base64FileName = 'Base64TestCV.rtf';
const postedFileName = 'TestCV.rtf';
const method = 'POST';
const mimeType = 'application/rtf';
cy.fixture(base64FileName).as('base64File'); // file content in base64
cy.get('#multipartForm').then((response) => {
const formData = new FormData();
formData.append('firstFormField', 'Hello');
formData.append('secondFormField', '25');
const blob = Cypress.Blob.base64StringToBlob(this.base64File, mimeType);
formData.append('uploadFile', blob, postedFileName);
cy.multipartFormRequest(method, postUrl, formData, function (response) {
expect(response.status).to.eq(200);
expect(response.response).to.match(/MyCypressTestCV/); // http://httpbin.org/post reflects what you post
});
});
});
});
While waiting for the built-in support, we can stop copy-pasting the hack and create a reusable package out of it.
Feel free to open issues so we can make it suitable for the most popular use cases.
Note that's more of just uploading a file and not focused on a form specifically.
https://www.npmjs.com/package/cypress-file-upload

Download image from express route

I have an express server running with the following route:
exports.getUserFile = function (req, resp) {
let filePath = path.join(__dirname, 'storage', req.params.fileName);
resp.download(filePath);
});
}
In my web app i'm calling this route and trying to save the file locally using file-saver:
let req = request.get('/users/' + userId + '/files/' + file.name);
req.set('Authorization', 'Bearer ' + this.state.jsonWebToken);
req.end((err, resp) => {
let f = new File([resp.text], file.name, {type: resp.type});
fileSaver.saveAs(f);
});
If the file is plain text then it works ok, but for other file types like images i'm not able to open the file (it's 'corrupt').
This is what the response looks like:
Do I need to decode the data in some way first? What is the correct way to save the content of the file?
If you're using superagent to perform the requests, you can explicitly set the response type to "blob", which would prevent any attempts to decode the response data. The binary data will end up in resp.body:
req.responseType('blob').end((err, resp) => {
saveAs(resp.body, file.name);
});
I haven't used express for a long time ago and I'm typing from mobile, it's seems a encoding issue, so it's seems that you're a sending raw image, you will need to encode it in base64 try something like:
//Here your saved file needs to be encoded to base 64.
var img = new Buffer(data, 'base64');
res.writeHead(200, {
'Content-Type': 'image/png',
'Content-Length': img.length
});
res.end(img);
Where data is your saved image, If you can render the image you just add the headers for download or just chain method download.
If you want to download the image as attachment in the page you can use res
exports.getUserFile = function (req, resp) {
let filePath = path.join(__dirname, 'storage', req.params.fileName);
var check = fs.readFileSync(__dirname+req.params.fileName);
resp.attachment(req.params.fileName); // The name of the file to be saved as. Eg Picture.jpg
res.resp(check) // Image buffer read from the path.
});
}
Reference:
http://expressjs.com/en/api.html#res.attachment
http://expressjs.com/en/api.html#res.end
Hope this helps.

Send data to a nodeJs server using Ajax and busboy

I'm having some trouble sending data via Ajax to upload a file, if I send the data from the form directly, it works with the following code:
var fs = require('fs');
var fstream;
var folder = 'public/images/tmp/';
var path;
var images = [];
req.pipe(req.busboy);
req.busboy.on('file', function (fieldname, file, filename) {
if(filename){
if (!fs.existsSync(folder)) {
fs.mkdirSync(folder,0744);
}
path = folder+ filename;
fstream = fs.createWriteStream(path);
file.pipe(fstream);
} else {
path= undefined;
}
images.push(path);
res.send(images);
});
And the files are sent to the temp folder in the server. But I need to return images to my javascript, so with that data I can perform other actions.
Now, the options I am thinking are:
1- Find some way to avoid "data.send()" to redirect my form to the result view
2- Send the data with Ajax using a code similar to:
$.ajax( {
url: '/media',
type: 'POST',
data: new FormData(document.getElementById('#myFormId')),
success: //do things,
error: //do other things
});
but it doesn't work, I don't know wich format I should send my data to the POST function so it can work with busboy or if there is another way to copy the files to the server.
Thanks.

Categories