Convert input files to byte[] javascript - javascript

I'm working on a REST web application that manages documents between users and uploaders. The backend is written in Java and my Document entity contains, besides various attributes, a byte[] content. I was able to send a file created at server side by
#GET
...
document.setContent(Files.readAllBytes(Paths.get("WEB-INF/testFile.txt")));
return Response.ok(document).build();
and retrieve it at front-end (vueJs) through
async function download(file) {
const fileURL = window.URL.createObjectURL(new Blob([atob(file.content)]));
const fileLink = document.createElement("a");
fileLink.href = fileURL;
fileLink.setAttribute("download",`${file.name}.${file.type}`);
document.body.appendChild(fileLink);
fileLink.click();
fileLink.remove;
window.URL.revokeObjectURL(fileURL);
}
the problem is that when I try to upload a file and then download it, its content is not parsed correctly (is shown undefined, string in Base64 or numbers depending on how I try to solve it). The file is sent by a post request and is retrieved through an input form bound to an onFileSelected function.
function onFileSelected(e) {
var reader = new FileReader();
reader.readAsArrayBuffer(e.target.files[0]);
reader.onloadend = (evt) => {
if (evt.target.readyState === FileReader.DONE) {
var arrayBuffer = evt.target.result;
this.file.content = new Uint8Array(arrayBuffer);
//this.file.content = arrayBuffer;
}
};
}
axios.post(...,document,...)
and I have tried using atob and btoa as well before assigning the value to this.file.content. If I print the file on server Welcome.txt it gives B#ae3b74d and if I use Arrays.toString(welcome.getContent()) it gives an array of numbers but as soon as it passed to the frontend its content become in Base64 welcome: { ... content: IFRoaXMgaXMgYSB0ZXN0IGZpbGUhIAo...}. Any idea? Thank you a lot!

Related

Send a byte array to WCF service

I am trying to send a pdf file from javascript to a rest wcf service.
The service expects an array of byte with the following signature
The trick is in the byte array parameter, all the others are working fine
[OperationContract]
[WebInvoke(UriTemplate = "rest/{sessionToken}/ImportNewTemplate?commit={commit}&createApplication={createApplication}&templateName={templateName}&option={option}")]
[CloudMethod(Group = "02. Templates", Description = "Import a new template in the platform.", HelpFile = "ListPaperTemplate.aspx")]
[CloudParameter(Name = "sessionToken", Description = "session token", HelpFile = "ServiceAPIDoc.aspx?q=sessionToken")]
[CloudParameter(Name = "createApplication", Description = "Create a standalone application linked to this template.")]
[CloudParameter(Name = "commit", Description = "Commit the upload ? if true, the template will be imported, else the return just allow you to preview template description.")]
[CloudParameter(Name = "templateName", Description = "Name of the new template. Only valid for single pdf upload. If the files are zipped, the file name in the zip will be used instead")]
[CloudParameter(Name = "templateFile", Description = "Can be a PDF file, or a zip file containing a flat pdf + xml definition", HelpFile = "ServiceAPIDoc.aspx?q=templateFile")]
CloudObjects.TemplateImportation ImportNewTemplate(string sessionToken, bool commit, bool createApplication, byte[] templateFile, string templateName, string option);
this is what I use from the javascript end to send the pdf file
const file = e.target.files[0];
// Encode the file using the FileReader API
const reader = new FileReader();
var fileByteArray = [];
reader.onloadend = async (e) => {
const arrayBuffer = e.target.result,
array = new Uint8Array(arrayBuffer);
for (const a of array) {
console.log(a);
fileByteArray.push(a);
}
let ret = await dispatch('createTemplate', {name: this.newForm.name, pdf:fileByteArray, save:false});
await this.$store.dispatch('hideLoadingScreen')
// Logs data:<type>;base64,wL2dvYWwgbW9yZ...
};
reader.onerror = async () => {
await this.$store.dispatch('hideLoadingScreen')
}
reader.onabort = async () => {
await this.$store.dispatch('hideLoadingScreen')
}
await this.$store.dispatch('showLoadingScreen');
reader.readAsArrayBuffer(file);
And here is the code to send it to the rest service
let url = `${getters.getServiceUrl}ImportNewTemplate?templateName=${name}&commit=${save || true}`
const xhr = new XMLHttpRequest;
xhr.open("POST", url, false);
xhr.setRequestHeader('Content-Type', 'application/json');
let response = await xhr.send(pdf);
However every time I get an error from the service when it tries to deserialise the byte array.
The exception message is 'There was an error deserializing the object of type System.Byte[]. End element 'root' from namespace '' expected.
I have tried a lot of alternatives but nothing works.
Any suggestions are welcome !
Thanks
For those interested, the trick was to add JSON.stringify to the returned array.
So: xhr.send(JSON.stringify(pdf))
would do the trick

How to send both text and binary data in axios post request?

I would need to find a solution to send via a single axios POST request both of the following:
json structure
binary file (excel file)
How can I achieve this?
let files = event.target.files;
const fileReader = new FileReader();
fileReader.readAsText(files[0], null);
fileReader.onload = () => {
this.fileContent = fileReader.result;
let binaryDataForObject = this.fileContent;
let referenceDataStructure = {
textData: textDataForObject,
binaryData: binaryDataForObject,
referenceDataFileExtension: this.referenceDataFileExtension,
userProvidedDataTypes: this.columnTypes
};
}
this.axios
.post(
"http://url,
referenceDataStructure
)
This works technically but on the java side I couldn't figure out, how to decode the binary data (encoded as a string) so that it is treated as an excel file.
Thank You in advance for any meaningful responses.
Lubos.
With simple POST request you can send only up to 1mb of binary data
To send binary and text in one request you should use FormData
Check out this answer for information
Update 14.12
How I managed to do this in my recent project was using FormData
So firstly you need to get file as a blob:
const fileReader = new FileReader()
// Here we will get the file as binary data
fileReader.onload = () => {
const MB = 1000000;
const Blob = new Blob([fileReader.result], {
// This will set the mimetype of the file
type: fileInputRef.current.files[0].type
});
const BlobName = fileInputRef.current.files[0].name;
if (Blob.size > MB) return new Error('File size is to big');
// Initializing form data and passing the file as a param
const formData = new FormData();
// file - field name, this will help you to read file on backend
// Blob - main data to send
// BlobName - name of the file, default it will be name of your input
formData.append('file', Blob, BlobName);
// Append json data
formData.apped('some-key', someValue)
// then just send it as a body with post request
fetch('/api/submit-some-form-with-file', {
method: 'POST',
body: formData
})
// Handle the rest
.then()
}
fileReader.readAsArrayBuffer(fileInputRef.current.files[0])
You can wrap this example in handle submit function in react and like or use it as is

Converting <input type=file> contents to Base64 and sending to Spring method expecting MultiPartFile

Because of server issues I need to convert the contents of a file upload to Base64 before it gets submitted to the server.
I've managed the JS side of things using reader.readAsDataURL to get the contents into Base64 in a local JS variable. I've then tried creating a new FormData and setting the base64 variable there - it replaces the name of the but then it also replaces the type - it's no longer a but just binary data - so when I send this to the server - I'm getting Spring error typeMismatch.org.springframework.web.multipart.MultipartFile
Basically - any thoughts how to convert file contents to Base64 (done that ok) but send to existing JAVA method with Spring MultiPartFile?
i.e. without me rewriting and adding extra fields in the FormData for file name and size etc (the stuff I'd get using the MultipartFile on the server end).
JS: (error handling removed)
var input = $(".actualFileInput");
var files = null;
// File data
if (input[0].files.length > 0) {
files = input[0].files;
}
var file = files[0], reader = new FileReader();
reader.onloadend = function () {
var b64 = reader.result.replace(/^data:.+;base64,/, '');
var name = input.attr('name');
input.attr('name', '');
var newFormData = new FormData(form); // New form with all data from the existing one
newFormData.set('uploadFile',b64); // replace with the base64 value of the selected file
var request = new XMLHttpRequest();
request.onreadystatechange = function () {
request.open(form.method, form.action, true);
request.onload = function() {
var url = window.location;
input.attr('name', name);
request.send(newFormData);
};
reader.readAsDataURL(file);
The Java at the server end:
#RequestMapping(method = RequestMethod.POST, params = "upload")
public String upload(#ModelAttribute("uploadDocument") UploadDocument document, BindingResult result,
ModelMap model, HttpServletRequest request, SessionStatus status) throws Exception {
UploadDocument is:
public class UploadDocument implements Serializable {
private static final long serialVersionUID = -3534003705995293025L;
// File upload work area
private MultipartFile uploadFile = null;
private String fileComment = null;
private Integer fileTypeId = null;
... (other fields in the <form>)
All the JAVA stuff works fine if I just submit the form. But in JS reading the file contents as Base64 then sending as a field doesnt get translated to MultiPartFile. Change to byte[] and add new fields for the file metadata myself? Or is there some trick I'm missing.
Thanks folks.
The way to make this JS variable send to a Spring MultiPartFile is:
newFormData.set('uploadFile',new Blob([b64]),files[0].name); // replace with the base64 value of the selected file
i.e. make it a blob, and 3rd arg is the file name the user selected in . This now sends the base64 value of the file contents.

Insert bytes[] in database (Hibernate, JPA, Spring) from input type=file

I've been fighting for days with this problem:insert file into database with Spring Boot, JPA Hibernate and Vue.js frontend.
(Yes I know it's better to store the location to retrieve the file and not the file itself, but I have to store the file, so move on.) I tried different solutions but I didn't manage. First I passed the file path from fontend to backend as a normal field of my json data and used:
String path= json.get("file_name").asText();
File file = new File(path);
byte[] fileInBytes = new byte[(int) file.length()];
FileInputStream fileInputStream = new FileInputStream(file);
fileInputStream.read(fileInBytes);
fileInputStream.close();
c.setFile(fileInBytes);
It worked only if I passed the explicit path, because from my HTML input type=file I always got C:/fakepath/filename and the backend didn't find the file obviously. Is there any way to pass the explicit path? I've searched for a while but I couldn't find a solution, so I changed my mind.
Now I'm passing the base64 encode of the file from the frontend in Vue with this code (thanks to How to convert file to base64 in JavaScript?):
getBase64(file, onLoadCallback) {
return new Promise(function(resolve, reject) {
var reader = new FileReader();
reader.onload = function() { resolve(reader.result); };
reader.onerror = function(error) {
console.log('Error when converting file to base64: ', error);
};
reader.readAsDataURL(file);
});
},
uploadFile: function(event) {
this.input_file = document.getElementById("challenge_file").files[0];
this.base64_file = this.getBase64(this.input_file);
this.base64_file.then(function(result) {
console.log(result);
this.File=JSON.stringify({'file': this.base64_file});
axios.post("/uploadfile",
this.File,
{ headers: {
'Content-type': 'application/json',
}
}).then(function(response){
location.reload(true);
}).catch(function (error){
console.log(error);
});
});
}
this code works and I get a base64 string also in the backend, in which I try to convert it in bytes[] because my file is a #Lob private byte[] file;. This is my code in the backend controller:
System.out.println(json.get("file"));
//print "data:text/plain;base64,aVZCT1J..."
int init= json.get("file").asText().lastIndexOf(",");
String base64file=json.get("file").asText().substring(init+1);
//I get only the part after 'base64,' *(see below)
System.out.println(base64file);
byte[] decodedByte = Base64.getDecoder().decode(base64file);
//I decode it into bytes[]
c.setFile(decodedByte);
*I get only the the part after 'base64,' otherwise if I use all the String I get this error: enter java.lang.IllegalArgumentException: Illegal base64 character 3a
This code has no errors, but the Blob in the database is empty, while in the first way I could open the file preview from Hibernate, but only if I wrote the correct real path, not retrieving it from the input.
Any suggestion? What should I do?
SOLVED:
Thanks to an answer to this question I changed my backend into:
System.out.println(json.get("file"));
String data= json.get("file").asText();
String partSeparator = ",";
if (data.contains(partSeparator)) {
String encodedImg = data.split(partSeparator)[1];
byte[] decodedImg = Base64.getDecoder().decode(encodedImg.getBytes(StandardCharsets.UTF_8));
c.setFile(decodedImg);
}
and now I see the correct file in the db.

Chrome crashes when exporting file via Filesystem API

Im trying to run in-browser encryption application which uses jQuery 1.10.2 and CryptoJS 3.2.1
the problem that I face starts at around 2mb files. File can be encrypted just fine, but when a data URI is created for the file it crashes the browser.
I would like a way around this to make it possible to encrypt files up-to 50mb's without browser crashing.
Here is the current snippt responsible for file saving via FileReader API
var reader = new FileReader();
if(body.hasClass('encrypt')){
// Encrypt the file!
reader.onload = function(e){
// Use the CryptoJS library and the AES cypher to encrypt the
// contents of the file, held in e.target.result, with the password
var encrypted = CryptoJS.AES.encrypt(e.target.result, password);
// The download attribute will cause the contents of the href
// attribute to be downloaded when clicked. The download attribute
// also holds the name of the file that is offered for download.
a.attr('href', 'data:application/octet-stream,' + encrypted);
a.attr('download', file.name + '.encrypted');
step(4);
};
// This will encode the contents of the file into a data-uri.
// It will trigger the onload handler above, with the result
reader.readAsDataURL(file);
}
else {
// Decrypt it!
reader.onload = function(e){
var decrypted = CryptoJS.AES.decrypt(e.target.result, password)
.toString(CryptoJS.enc.Latin1);
if(!/^data:/.test(decrypted)){
alert("Invalid pass phrase or file! Please try again.");
return false;
}
a.attr('href', decrypted);
a.attr('download', file.name.replace('.encrypted',''));
step(4);
};
reader.readAsText(file);
}
What can I change in above code to allow for larger files to be encrypted and decrypted?
Live site: droplet.so (currently capped at 1.5mb otherwise browser crash is guaranteed)
Kindly thanks in advance.
With a little research I found out that 1.99MB is the maximum the can be saved in the data url in chrome.
Your problem can be done by converting your data url to blob
You can find more information here:
Blob from DataURL?
Chrome crashes when URI is too long is here a similar post ( see second answer ).
EDIT:
Possible solution
function dataURItoBlob(dataURI) {
var byteString = atob(dataURI.split(',')[1]);
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]
var ab = new ArrayBuffer(byteString.length);
var ia = new Uint8Array(ab);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
var bb = new BlobBuilder();
bb.append(ab);
return bb.getBlob(mimeString);
}
function download(dataURI) {
var blob = dataURItoBlob(dataURI);
var url = window.URL.createObjectURL(blob);
window.location.assign(url);
}
And you can use this code by calling download(dataURI).

Categories