Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Downloading a file with content type Content-Type:multipart/mixed

I am doing an angular application where i have to hit a rest end point and download the file that is sent as the response but i am not understanding how to go about this. I have the response header like the following

Content-Disposition:attachment; filename="Config.zip" Content-Type:multipart/mixed;boundary=Boundary_25_1816124633_1519993185650 MIME-Version:1.0 Transfer-Encoding:chunked

and the response looks like

--Boundary_25_1816124633_1519993185650 Content-Type: application/json

{"config":[{},{},{}]} --Boundary_25_1816124633_1519993185650 Content-Type: application/octet-stream

PKMÛJAä;%RecurrenceEvent_CreateContract_1.jsoníYKoãF¾ÈxÝ0è÷Ã7Mb L&íÝK0ͦCD¬1ðß(J¤HÙ²¼yV'»ÙU¬®úªú«â· ö«åºv~\Í~ùöýw³Ù,È«ù

EDIT

this is my http call thats ging to the backend

return this.http.get(url).map(response => {
// TODO
});

How to download the zip file that is being attached ? Please help. I am stuck.

like image 660
Vikhyath Maiya Avatar asked Mar 02 '18 12:03

Vikhyath Maiya


2 Answers

const executeSaveAs = (content) => {
    let blob = new Blob([content], {'type': "application/octet-stream"});
    saveAs(blob, "downloaded_zip.zip"); // This is from https://github.com/eligrey/FileSaver.js
};

return this.http.get(url, {responseType: 'arraybuffer'}).pipe(executeSaveAs);

We need to set the expected response type, which we'd want to be 'arraybuffer'. Then, we do the usual stuff for FileSaver, that is creating a blob and passing it to the package's saveAs function.

Edit

As per the comment, a clarification was asked for parsing the various parts of the multipart response.

Info about the Multipart content type

The boundary, as defined in the header, indicates the different parts of the response. Each of the parts are preceded by the line

--boundaryThatIsInTheHeader

Method

We can split the response as per the boundary. To do this, we must first parse the boundary from the header. RegEx to our rescue here:

let boundaryRegex = new RegExp(/boundary=(\S+)/g);
const header = `Content-Disposition:attachment; filename="Config.zip" Content-Type:multipart/mixed;boundary=Boundary_25_1816124633_1519993185650 MIME-Version:1.0 Transfer-Encoding:chunked`; // As in the question

const boundary = '--' + boundaryRegex.exec(header)[1]; // We obtain the boundary here

Now, we need to split the response with the boundary as the delimiter.

response.split(boundary);

The value returned for this specific response of the server would be

[ "", " Content-Type: application/json\n\n {\"config\":[{},{},{}]} ", " Content-Type: application/octet-stream\n\n PKMÛJAä;%RecurrenceEvent_CreateContract_1.jsoníYKoãF¾ÈxÝ0è÷Ã7Mb L&íÝK0ͦCD¬1ðß(J¤HÙ²¼yV'»ÙU¬®úªú«â· ö«åºv~Í~ùöýw³Ù,È«ù"]

Notice the second element of the array, that's the JSON. That's what we desired! We can now remove the extra data, i.e., content type, by using simple RegEx. If the format of the response is constant, we can also directly remove it as per the index. Same is the case for the zip file's content.

like image 71
Shiven Sinha Avatar answered Oct 10 '22 10:10

Shiven Sinha


I believe multipart/mixed and multipart/form-data share some common structure.
(not exactly sure what the difference is.)

form-data is mostly used for sending forms to the server but could as well be used the other way around.

the fetch api has a method called .formData()

This is mainly relevant to service workers. If a user submits a form and a service worker intercepts the request, you could for example call formData() on it to obtain a key-value map, modify some fields, then send the form onwards to the server (or use it locally).

So if we can get the response and change the content-type header to multipart/form-data we could utilize the fetch api to read the content without having to parse it.

getExampleResponse(async res => {
  // replace the content-type to multipart/form-data so fetch api can parse it
  const type = res.headers.get('content-type')
  res.headers.set('content-type', type.replace('mixed', 'form-data'))

  // return the response as a formData
  const fd = await res.formData()

  // console.log(...fd)
  console.log(JSON.parse(fd.get('json')))
  console.log(fd.get('image'))

  const file = fd.get('image')
  const link = document.createElement('a')
  const image = new Image
  link.href = image.src = URL.createObjectURL(file)
  link.innerText = 'download ' + (link.download = file.name)
  
  // saveAs(file); // This is from https://github.com/eligrey/FileSaver.js

  document.body.appendChild(image)
  document.body.appendChild(link)  
})




/* 
Don't mind this, it's just an example response you are getting...
What you actually want is something like 

function getExampleResponse(cb) {
  fetch(url).then(cb)
}
*/
function getExampleResponse(e){var a=document.createElement("canvas"),d=a.getContext("2d");d.fillStyle="blue";d.fillRect(0,0,a.width,a.height);a.toBlob(function(b){var a={a:123};var c='--Boundary_25_1816124633_1519993185650\r\nContent-Disposition: form-data; name="json"\r\nContent-Type: application/json\r\n'+("Content-Length: "+JSON.stringify(a).length+"\r\n\r\n");c+=JSON.stringify(a)+"\r\n";c=c+'--Boundary_25_1816124633_1519993185650\r\nContent-Disposition: form-data; name="image"; filename="image.png"\r\nContent-Transfer-Encoding: binary\r\n'+
("Content-Type: "+b.type+"\r\n");c+="Content-Length: "+b.size+"\r\n\r\n";b=new Blob([c,b,"\r\n--Boundary_25_1816124633_1519993185650--\r\n"]);e(new Response(b,{headers:{"Content-Type":"multipart/mixed; boundary=Boundary_25_1816124633_1519993185650"}}))})};
like image 23
Endless Avatar answered Oct 10 '22 10:10

Endless