Download ANSI zip file in front end, generated by C# back end - javascript

I have a C# backend generating a zip file in memory (with System.IO.Compression) and sending it to my front end. If I download the zip file before sending it, it is working well and is in ANSI encoding (found on notepad++).
This is how I return my file currently, I have tried many different ways to do it, such as simply returning a file without headers, but right now it looks like this :
[HttpPost]
[Route("GetUserWorkContext")]
public async Task<IActionResult> GetUserWorkContext([FromBody] GetUserWorkContextRequest request, [FromServices] IExportManager exportManager)
{
var zipStream = await exportManager.GetUserWorkContext(userId, request.IncludeArchived);
HttpContext.Response.Headers.Add("Content-Disposition", "attachment; filename = test.zip; charset=Windows-1252");
HttpContext.Response.Headers.Add("Content-Length", zipStream.ToArray().Length.ToString());
return File(zipStream.ToArray(), "application/octet-stream");
}
It seems that no matter how I download the file with Javascript (front-end), it is saved with utf8 encoding (found with notepad++ again). I tried using js-file-download ( https://www.npmjs.com/package/js-file-download ) or creating blobs, but anything I end up downloading is encoded in utf8.
How should I go about downloading this file in Javascript without corrupting the archive ?
Here is my current attempt in Javascript, using a piece of code I found here (JavaScript blob filename without link) to download the file :
function getUserWorkContext({ includeArchived }) {
return new Promise(function () {
Axios.post('/api/Export/GetUserWorkContext', {includeArchived})
.then((response) => {
if(response.data){
var blobObject = new Blob([response.data], {type: 'application/zip;charset=Windows-1252'});
downloadFile(blobObject, "test.zip");
}
})
}
function downloadFile(file, fileName) {
if (navigator.msSaveBlob) { // For ie and Edge
return navigator.msSaveBlob(file, fileName);
}
else {
let link = document.createElement('a');
link.href = window.URL.createObjectURL(file);
link.download = fileName;
document.body.appendChild(link);
link.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));
link.remove();
window.URL.revokeObjectURL(link.href);
}
}
Note : the actual zip is 3,747KB where as the download zip from Javascript is always much bigger, in this case : 6,917KB.

This is a problem with axios:
I guess, you should use blob or arraybuffer as responseType for axios.
{ responseType: 'blob' }
// responseType indicates the type of data that the server will
respond with // options are: 'arraybuffer', 'document', 'json',
'text', 'stream' // browser only: 'blob'
responseType: 'json' // default
Check this answer: https://stackoverflow.com/a/60461828/2487565
=== 8< ======================= previous version ======================= 8< ===
Your Content-Disposition header is wrong. There is no charset parameter for Content-Disposition header.
Check the docs: MDN HTTP Content-Disposition
That's why your file is still sent in UTF-8, since your charset parameter has no effect.
To use UTF-8:
Delete both Content- headers from C# and the charset parameter from JavaScript
var blobObject = new Blob([response.data], {type: 'application/zip'});
If you really need to use Windows-1252, you can try to set it with the content type parameter.
return File(zipStream.ToArray(), "application/octet-stream;charset=Windows-1252");
Check this also: Charset Encoding in ASP.NET Response
By the way, UTF-8 is the preferred charset encoding: W3 QA choosing encodings
And, yes #nikneem, there is no need in the Content-Disposition and Content-Length headers. They will be generated automatically.

Related

Downloading a zip file from a byte array

I have an api which sends a zip file as a byte array (not the byte arrays of the individual files, but the zipped file on the whole). When I trigger the api in postman, i get random characters (as shown below).
When I download this response (as option in postman: send to a file and download) in a zip file, I am able to unzip it and extract the actual files. My goal is to achieve the same thing in angular and typescript.
I have tried to convert the response to a blob and download it, as suggested in multiple places online, including this question. So I did something like
const blob = new Blob([response], { type: 'application/zip' });
const url = window.URL.createObjectURL(blob);
window.open(url);
But the resultant zip file I download says 'unable to open: empty archive'. I am not sure what I am missing here. I tried converting the response to arrayBuffer (using this) first before applying the steps as well, as that was suggested in another place online. But that hasn't been of use either.
Can someone please help me understand what I'm doing wrong. Thanks
I am calling the API in a js file:
function downloadAzureRT(params) {
return $http({
method: 'GET',
url: API.public('protectionSources/downloadArtFile'),
params: params || {},
}).then(function downloadAwsARTResp(resp){
return resp.data || {};
});
}
And then calling this function in a ts file.
downloadART() {
this.ajsPubSourceService.downloadAzureRT({
filePath: ART_FILE_PATH,
fileName: ART_FILE_NAME,
})
.then((response) => {
const blob = new Blob([response], { type: 'application/zip' });
const url = window.URL.createObjectURL(blob);
window.open(url);
}

Content-Disposition: download file automatically

The API call to the server is returning a zip file with Content-Disposition in format attachment, <filename>
I am using FileSaver's saveAs to save the file.
this.ajax.raw(requestUrl, {
dataType: 'binary',
xhr: () => {
const myXhr = $.ajaxSettings.xhr()
myXhr.responseType = 'blob'
return myXhr
}
}).then((response) => {
this.downloadSuccess(response, minTimeString, maxTimeString, downloadCompletedMessage)
}).catch((e) => {
this.downloadError(e)
})
downloadSuccess (response, minTime, maxTime, downloadCompletedMessage) {
const filename = (response.jqXHR.getResponseHeader('Content-Disposition').split('"')[1])
saveAs(response.payload, filename, 'application/zip')
This works fine for small files but fails if the file is more than 2Gb (The file is downloaded successfully but the saved file is of 1Kb only).
During my research, I saw that browser can download the file without FileSaver if the response has Content-Disposition which is true in my case. But I am not able to figure out how.
Do I need to use request differently?
From docs:
Content-Disposition attachment header is the best preferred way to
download files from the browser. It has better cross browser
compatibility, won't have any memory limit and it doesn't require any
JavaScript.
You don't need ajax request to download the file. Only ensure that server add Content-Disposition header and provide a link to download.
If you can also use the anchor download attribute from HTML5.
Causes the browser to treat the linked URL as a download.
const link = document.createElement('a');
link.href = '/xyz/abc.pdf';
link.download = "file.pdf";
link.dispatchEvent(new MouseEvent('click'));

Google Drive API: Correct way to upload binary files via the Multipart API

I'm trying to upload a binary file to Google Drive via the
multipart upload API v3.
Here's the hex representation of the content of the file:
FF FE
For some reason the above content gets encoded as UTF-8 (I assume)
when I try to POST it, enclosed in a multipart payload:
--BOUNDARY
Content-Type: application/json
{"name": "F.ini"}
--BOUNDARY
Content-Type: application/octet-stream
ÿþ <-- in the outbound request, this gets UTF-8 encoded
--BOUNDARY--
Hex representation of the file that ultimately gets stored on server side:
C3 BF C3 BE
The problem only occurs in the sending stage:
if I check the length of the content read from the file I always get 2;
regardless of whether I use FileReader#readAsBinaryString or FileReader#readAsArrayBuffer
(producing a string with length 2, and an ArrayBuffer with byteLength 2, respectively).
Here's the minimal code that I'm using to generate the multipart payload:
file = picker.files[0]; // 'picker' is a file picker
reader = new FileReader();
reader.onload = function (e) {
content = e.target.result;
boundary = "BOUNDARY";
meta = '{"name": "' + file.name + '"}';
console.log(content.length); // gives 2 as expected
payload = [
"--" + boundary, "Content-Type: application/json", "", meta, "", "--" + boundary,
"Content-Type: application/octet-stream", "", content, "--" + boundary + "--"
].join("\r\n");
console.log(payload.length); // say this gives n
xhr = new XMLHttpRequest();
xhr.open("POST", "/", false);
xhr.setRequestHeader("Content-Type", "multipart/related; boundary=" + boundary);
xhr.send(payload); // this produces a request with a 'Content-Length: n+2' header
// (corresponding to the length increase due to UTF-8 encoding)
};
reader.readAsBinaryString(file);
My question is twofold:
Is there a way to avoid this automatic UTF-8 encoding? (Probably not, because
this answer
implies that the UTF-8 encoding is part of the XHR spec.)
If not, what is the correct way to "inform" the Drive API that my file content is UTF-8 encoded?
I have tried these approaches, with no success:
appending ; charset=utf-8 or ; charset=UTF-8 to the binary part's Content-Type header
doing the same to the HTTP header on the parent request
(Content-Type: multipart/related; boundary=blablabla, charset=utf-8;
also tried replacing the comma with a semicolon)
I need the multipart API because AFAIU the "simple" API
does not allow me to upload into a folder
(it only accepts a filename as metadata, via the Slug HTTP header,
whereas the JSON metadata object in the multipart case allows a parent folder ID to be specified as well).
(Just thought of mentioning this because the "simple" API handles things correctly
when I directly POST the File (from the picker) or ArrayBuffer (from FileReader#readAsArrayBuffer) as the XHR's payload.)
I do not want to utilize any third-party libraries because
I want to keep things as light as possible, and
keeping aside reinventing-the-wheel and best-practices stuff, anything that is accomplished by a third party library should be doable via plain JS as well (this is just a fun exercise).
For the sake of completeness I tried uploading the same file via the GDrive web interface, and it got uploaded just fine;
however the web interface seems to base64-encode the payload, which I would rather like to avoid
(as it unnecessarily bloats up the payload, esp. for larger payloads which is my eventual goal).
How about this modification?
Modification points:
Used new FormData() for creating the multipart/form-data.
Used reader.readAsArrayBuffer(file) instead of reader.readAsBinaryString(file).
Send the file as a blob. In this case, the data is sent as application/octet-stream.
Modified script:
file = picker.files[0]; // 'picker' is a file picker
reader = new FileReader();
reader.onload = function (e) {
var content = new Blob([file]);
var meta = {name: file.name, mimeType: file.type};
var accessToken = gapi.auth.getToken().access_token;
var payload = new FormData();
payload.append('metadata', new Blob([JSON.stringify(meta)], {type: 'application/json'}));
payload.append('file', content);
xhr = new XMLHttpRequest();
xhr.open('post', 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart');
xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken);
xhr.onload = function() {
console.log(xhr.response);
};
xhr.send(payload);
};
reader.readAsArrayBuffer(file);
Note:
In this modified script, I put the endpoint and the header including the access token. So please modify this for your environment.
In this case, I used a scope of https://www.googleapis.com/auth/drive.
Reference:
Using FormData Objects
In my environment, I could confirmed that this script worked. But if this didn't work in your environment, I'm sorry.

Dealing with encoding in Flask file uploads/downloads

I have a react client that takes in user input as a file and sends it to my remote Flask server for storage. I send the file in the form of a Werkzeug FileStorage object and in the remote server I store it with file.save(path). In the react client I'm trying to build a way to download the file from the server, however I'm running into problems. Currently my program is working for downloading .txt files. I'm able to do this though a fetch javascript request:
fetch(FETCH_URL, {
method: 'POST',
body: data,
headers: {
'Content-Type': 'application/json'
}
}).then((response) => {
var a = response.body.getReader();
a.read().then(({ done, value }) => {
saveAsFile(new TextDecoder("utf-8").decode(value), 'filename.txt');
}
);
});
function saveAsFile(text, filename) {
const type = 'application/text'; // modify or get it from response
const blob = new Blob([text], {type});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
}
Thanks to some help I got in this post: Download file in react client from flask remote server
I know this code is specifically made to work only with .txt files based on the type being passed in to Blob, but the front end is not the real problem.
The real problem is in my remote flask server, the following code is what is called in the flask server:
with open(filename, 'r') as f:
contents = f.read()
return contents
I tried returning the file itself but the server gives an error:
"ValueError: I/O operation on closed file."
So I decided to return the contents of the file as shown above.
The problem arises when I try to get a file for example "download.jpeg". Reading the file gives the following error:
"UnicodeDecodeError: 'utf-8' codec can't decode byte 0x89 in position 0: invalid start byte"
From what I understand Flask works exclusively with 'utf-8' and I assume this means the file in the server is on 'utf-8' encoded.
Does anyone have a suggestion or guidance on a solution or a workaround maybe a way to change the files encoding when I save it on the server or something else that could help me with what I'm trying to do?
Fetch's Response has blob() to convert the response directly to blob, so you don't have to read the stream, you don't have to find out it's content type or anything. Just try the below solution.
fetch(FETCH_URL, {
method: 'POST',
body: data,
headers: {
'Content-Type': 'application/json'
}
}).then((response) => {
response.blob().then((blob) => {
saveBlob(blob, 'filename');
});
});
function saveBlob(blob, filename) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
}
Try this: make sure to install axios. Also you probably won't have to deal with content type like above said. Obviously changing the method type to POST and bring ur data in.
axios(FETCH_URL, {
method: 'GET',
responseType: 'blob', // important
}).then((response) => { //Creates an <a> tag hyperlink that links the excel sheet Blob object to a url for downloading.
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', `${Date.now()}.xlsx`); //set the attribute of the <a> link tag to be downloadable when clicked and name the sheet based on the date and time right now.
document.body.appendChild(link);
link.click(); //programmatically click the link so the user doesn't have to
document.body.removeChild(link);
URL.revokeObjectURL(url); //important for optimization and preventing memory leak even though link element has already been removed.
});

Download excel file in javascript from Rest API response content-disposition outputs [Object, Object]

I want to download a excel file from my angularJs code. Where i made a http post request to Java Rest API and returned the file with header
"Content-Disposition" : "attachment; filename=\"new_excel_file.xlsx\""
Java Code
#Post
#Path("/excel/trekResult")
#Produces("application/vnd.ms-excel")
public Response getResultsReport(#HeaderParam(Constants.ID) Long userId, #QueryParam(Constants.COMPANY_TREK_ID) Integer companyTrekId) {
String CONTENT_DESPOSITION = "Content-Disposition";
String CONTENT_ATTACHEMENT = "attachment; filename=\"new_excel_file.xlsx\"";
//Generates a excel file in local file system
File excelFile = misHelper.writeToFile(workBook, mis, userId, "trek-results");
return Response.ok().entity((Object)excelFile).
header(CONTENT_DESPOSITION, CONTENT_ATTACHEMENT).build();
}
On Javascript Side
myService.exportResult($scope.companyTrek.id).then(function(result) {
if(result !== undefined || result !== '') {
var blob = new Blob([result], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});
var objectUrl = URL.createObjectURL(blob);
saveAs(blob, 'Trek-Results-'+fetchCurrentDate()+ '.xlsx');
}
}
Used FileSaver.js to save file.
The output file is [Object, Object]
Tested The locally generated file.
Here is a similar question for reference that didn't help me.
receive an excel file as response in javascript from a Rest service
I just noticed the Mime types are different on Java server vs Angular client.
This link shows the different MIME types related to spreadsheets.
Try making them consistent and seeing if that fixed it.
There was also this way without mishelper.

Categories

Resources