Ajax file download sets filename to random string of characters - javascript

I am trying to download a file using ajax. The download works correctly, however, the downloaded filename is set to a random string of characters. I don't think this is relevant since the backend is working, but I'm using django
js/html:
<script>
function downloadFile(){
var filename = 'data.txt';
$.ajax({
url: 'downloadFile',
data: {'filename': filename},
success: function(blob, status, xhr){
var disposition = xhr.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('attachment') != -1){
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]){
filename = matches[1].replace(/['"]/g,'');
}
}
var bd = [];
bd.push(blob);
var typename = "application/" + filename;
var downloadURL = window.URL.createObjectURL(new Blob(bd, {type: typename}));
var a = document.createElement("a");
a.href = downloadURL;
document.body.append(a);
a.click();
}
});
}
</script>
...
<button id="downloadFile" type="button" onclick="downloadFile()"><i class="fa fa-download"></i></button>
...
django views.py:
import pathlib
import os
def downloadFile(request):
fname = request.GET.get('filename')
fpath = os.path.join(<local_filesystem_path>, fname)
# code to generate file here
if pathlib.Path(fpath).exists():
file_download = open(fpath, 'rb')
response = HttpResponse(file_download, content_type='application/{}'.format(fname))
response['Content-Disposition'] = 'attachment; filename="{}"'.format(fname)
return response
and urls.py:
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
path('downloadFile', views.downloadFile, name='downloadFile')
]
When I click the download button, everything works correctly except that my file has been renamed to a random string of 8 characters. Each time I re-download it, the string changes, but it's always 8 characters long. I'm guessing that something is happening where my browser thinks that the filename is unset so it's assigning a random string. But I'd like to set it to something I've specified - how can I do this?

Try adding a download attribute to the the <a> tag that you create. It allows you to set the file name.
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-download

Related

The web-application in the js-script creates a JSON-object. How can I save it as a file to my hard drive? [duplicate]

I have the following code to let users download data strings in csv file.
exportData = 'data:text/csv;charset=utf-8,';
exportData += 'some csv strings';
encodedUri = encodeURI(exportData);
newWindow = window.open(encodedUri);
It works just fine that if client runs the code it generates blank page and starts downloading the data in csv file.
So I tried to do this with JSON object like
exportData = 'data:text/json;charset=utf-8,';
exportData += escape(JSON.stringify(jsonObject));
encodedUri = encodeURI(exportData);
newWindow = window.open(encodedUri);
But I see only a page with the JSON data displayed on it, not downloading it.
I went through some research and this one claims to work but I don't see any difference to my code.
Am I missing something in my code?
Thanks for reading my question:)
This is how I solved it for my application:
HTML:
<a id="downloadAnchorElem" style="display:none"></a>
JS (pure JS, not jQuery here):
var dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(storageObj));
var dlAnchorElem = document.getElementById('downloadAnchorElem');
dlAnchorElem.setAttribute("href", dataStr );
dlAnchorElem.setAttribute("download", "scene.json");
dlAnchorElem.click();
In this case, storageObj is the js object you want to store, and "scene.json" is just an example name for the resulting file.
This approach has the following advantages over other proposed ones:
No HTML element needs to be clicked
Result will be named as you want it
no jQuery needed
I needed this behavior without explicit clicking since I want to trigger the download automatically at some point from js.
JS solution (no HTML required):
function downloadObjectAsJson(exportObj, exportName){
var dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(exportObj));
var downloadAnchorNode = document.createElement('a');
downloadAnchorNode.setAttribute("href", dataStr);
downloadAnchorNode.setAttribute("download", exportName + ".json");
document.body.appendChild(downloadAnchorNode); // required for firefox
downloadAnchorNode.click();
downloadAnchorNode.remove();
}
Found an answer.
var obj = {a: 123, b: "4 5 6"};
var data = "text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(obj));
$('download JSON').appendTo('#container');
seems to work fine for me.
** All credit goes to #cowboy-ben-alman, who is the author of the code above **
You could try using:
the native JavaScript API's Blob constructor and
the FileSaver.js saveAs() method
No need to deal with any HTML elements at all.
var data = {
key: 'value'
};
var fileName = 'myData.json';
// Create a blob of the data
var fileToSave = new Blob([JSON.stringify(data)], {
type: 'application/json'
});
// Save the file
saveAs(fileToSave, fileName);
If you wanted to pretty print the JSON, per this answer, you could use:
JSON.stringify(data,undefined,2)
This would be a pure JS version (adapted from cowboy's):
var obj = {a: 123, b: "4 5 6"};
var data = "text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(obj));
var a = document.createElement('a');
a.href = 'data:' + data;
a.download = 'data.json';
a.innerHTML = 'download JSON';
var container = document.getElementById('container');
container.appendChild(a);
http://jsfiddle.net/sz76c083/1
ES6+ version for 2021; no 1MB limit either:
This is adapted from #maia's version, updated for modern Javascript with the deprecated initMouseEvent replaced by new MouseEvent() and the code generally improved:
const saveTemplateAsFile = (filename, dataObjToWrite) => {
const blob = new Blob([JSON.stringify(dataObjToWrite)], { type: "text/json" });
const link = document.createElement("a");
link.download = filename;
link.href = window.URL.createObjectURL(blob);
link.dataset.downloadurl = ["text/json", link.download, link.href].join(":");
const evt = new MouseEvent("click", {
view: window,
bubbles: true,
cancelable: true,
});
link.dispatchEvent(evt);
link.remove()
};
If you want to pass an object in:
saveTemplateAsFile("filename.json", myDataObj);
Simple, clean solution for those who only target modern browsers:
function downloadTextFile(text, name) {
const a = document.createElement('a');
const type = name.split(".").pop();
a.href = URL.createObjectURL( new Blob([text], { type:`text/${type === "txt" ? "plain" : type}` }) );
a.download = name;
a.click();
}
downloadTextFile(JSON.stringify(myObj), 'myObj.json');
The following worked for me:
/* function to save JSON to file from browser
* adapted from http://bgrins.github.io/devtools-snippets/#console-save
* #param {Object} data -- json object to save
* #param {String} file -- file name to save to
*/
function saveJSON(data, filename){
if(!data) {
console.error('No data')
return;
}
if(!filename) filename = 'console.json'
if(typeof data === "object"){
data = JSON.stringify(data, undefined, 4)
}
var blob = new Blob([data], {type: 'text/json'}),
e = document.createEvent('MouseEvents'),
a = document.createElement('a')
a.download = filename
a.href = window.URL.createObjectURL(blob)
a.dataset.downloadurl = ['text/json', a.download, a.href].join(':')
e.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null)
a.dispatchEvent(e)
}
and then to call it like so
saveJSON(myJsonObject, "saved_data.json");
I recently had to create a button that would download a json file of all values of a large form. I needed this to work with IE/Edge/Chrome. This is what I did:
function download(text, name, type)
{
var file = new Blob([text], {type: type});
var isIE = /*#cc_on!#*/false || !!document.documentMode;
if (isIE)
{
window.navigator.msSaveOrOpenBlob(file, name);
}
else
{
var a = document.createElement('a');
a.href = URL.createObjectURL(file);
a.download = name;
a.click();
}
}
download(jsonData, 'Form_Data_.json','application/json');
There was one issue with filename and extension in edge but at the time of writing this seemed to be a bug with Edge that is due to be fixed.
Hope this helps someone
downloadJsonFile(data, filename: string){
// Creating a blob object from non-blob data using the Blob constructor
const blob = new Blob([JSON.stringify(data)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
// Create a new anchor element
const a = document.createElement('a');
a.href = url;
a.download = filename || 'download';
a.click();
a.remove();
}
You can easily auto download file with using Blob and transfer it in first param downloadJsonFile. filename is name of file you wanna set.
The download property of links is new and not is supported in Internet Explorer (see the compatibility table here). For a cross-browser solution to this problem I would take a look at FileSaver.js
If you prefer console snippet, raser, than filename, you can do this:
window.open(URL.createObjectURL(
new Blob([JSON.stringify(JSON)], {
type: 'application/binary'}
)
))
React: add this where you want in your render method.
• Object in state:
<a
className="pull-right btn btn-primary"
style={{ margin: 10 }}
href={`data:text/json;charset=utf-8,${encodeURIComponent(
JSON.stringify(this.state.objectToDownload)
)}`}
download="data.json"
>
DOWNLOAD DATA AS JSON
</a>
• Object in props:
<a
className="pull-right btn btn-primary"
style={{ margin: 10 }}
href={`data:text/json;charset=utf-8,${encodeURIComponent(
JSON.stringify(this.props.objectToDownload)
)}`}
download="data.json"
>
DOWNLOAD DATA AS JSON
</a>
className and style are optional, modify the style according to your needs.
Try to set another MIME-type:
exportData = 'data:application/octet-stream;charset=utf-8,';
But there are can be problems with file name in save dialog.
const exportToJson = (data: {}) =>{
const link = document.createElement("a");
link.href =
data:text/json;charset=utf8,${encodeURIComponent(JSON.stringify(data))}; link.download = 'example.json'; link.click(); }
Make sure to clean up the the created link after if you don't want a random element that does nothing.

Non-ASCII characters are not correctly displayed in PDF when served via HttpResponse and AJAX

I have generated a PDF file which contains Cyrillic characters (non-ASCII) with ReportLab. For this purpose I have used the "Montserrat" font, which support such characters. When I look in the generated PDF file inside the media folder of Django, the characters are correctly displayed:
I have embedded the font by using the following code in the function generating the PDF:
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
pdfmetrics.registerFont(TTFont('Montserrat', 'apps/Generic/static/Generic/tff/Montserrat-Regular.ttf'))
canvas_test = canvas.Canvas("media/"+filename, pagesize=A4)
canvas_test.setFont('Montserrat', 18)
canvas_test.drawString(10, 150, "Some text encoded in UTF-8")
canvas_test.drawString(10, 100, "как поживаешь")
canvas_test.save()
However, when I try to serve this PDF via HttpResponse, the Cyrillic characters are not properly displayed, despite being displayed in the Montserrat font:
The code that serves the PDF is the following:
# Return the pdf as a response
fs = FileSystemStorage()
if fs.exists(filename):
with fs.open(filename) as pdf:
response = HttpResponse(
pdf, content_type='application/pdf; encoding=utf-8; charset=utf-8')
response['Content-Disposition'] = 'inline; filename="'+filename+'"'
return response
I have tried nearly everything (using FileResponse, opening the PDF with with open(fs.location + "/" + filename, 'rb') as pdf...) without success. Actually, I do not understand why, if ReportLab embeddes correctly the font (local file inside media folder), the file provided to the browser is not embedding the font.
It is also interesting to note that I have used Foxit Reader via Chrome or Edge to read the PDF. When I use the default PDF viewer of Firefox, different erroneous characters are displayed. Actually the font seems to be also erroneous in that case:
Edit
Thanks to #Melvyn, I have realized that the error did not lay in the response directly sent from the Python view, but in the success code in the AJAX call, which I leave hereafter:
$.ajax({
method: "POST",
url: window.location.href,
data: { trigger: 'print_pdf', orientation: orientation, size: size},
success: function (data) {
if (data.error === undefined) {
var blob = new Blob([data]);
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = filename + '.pdf';
link.click();
}
}
});
This is the part of the code that is changing somehow the encoding.
Solution with the ideas from comments
I finally come up with a solution thanks to all the comments I have received, specially from #Melvyn. Instead of creating a Blob object, I have just set the responseType of the AJAX to Blob type. This is possible since JQuery 3:
$.ajax({
method: "POST",
url: window.location.href,
xhrFields:{
responseType: 'blob'
},
data: { trigger: 'print_pdf', orientation: orientation, size: size},
success: function (data) {
if (data.error === undefined) {
var link = document.createElement('a');
link.href = window.URL.createObjectURL(data);
link.download = filename + '.pdf';
link.click();
}
}
});
Handling an error when returning response
You can return an error from Python (i.e. catching an exception) as follows:
except Exception as err:
response = JsonResponse({'msg': "Error"})
error = err.args[0]
if error is not None:
response.status_code = 403 # To announce that the user isn't allowed to publish
if error==13:
error = "Access denied to the PDF file."
response.reason_phrase = error
return response
Then, you just have to use the native error handling from AJAX (after the success section):
error: function(data){
$("#message_rows2").text(data.statusText);
$('#errorPrinting').modal();
}
See further details in this link.
I hope this post helps people with the same problem while generating PDFs in non-ASCII (Cyrillic) characters. It took me several days...
You are doing some encoding/recoding, because if you look at the diff between the files, it's littered with unicode replacement characters:
% diff -ua Cyrillic_good.pdf Cyrillic_wrong.pdf > out.diff
% hexdump out.diff|grep 'ef bf bd'|wc -l
2659
You said you tried without setting the encoding and charset, but I don't think that was tested properly - most likely you saw an aggressively browser-cached version.
The proper way to do this is to use FileResponse, pass in the filename and let Django figure out the right content type.
The following is a reproducible test of a working situation:
First of all, put Cyrillic_good.pdf (not wrong.pdf), in your media root.
Add the following to urls.py:
#urls.py
from django.urls import path
from .views import pdf_serve
urlpatterns = [
path("pdf/<str:filename>", pdf_serve),
]
And views.py in the same directory:
#views.py
from pathlib import Path
from django.conf import settings
from django.http import (
HttpResponseNotFound, HttpResponseServerError, FileResponse
)
def pdf_serve(request, filename: str):
pdf = Path(settings.MEDIA_ROOT) / filename
if pdf.exists():
response = FileResponse(open(pdf, "rb"), filename=filename)
filesize = pdf.stat().st_size
cl = int(response["Content-Length"])
if cl != filesize:
return HttpResponseServerError(
f"Expected {filesize} bytes but response is {cl} bytes"
)
return response
return HttpResponseNotFound(f"No such file: {filename}")
Now start runserver and request http://localhost:8000/pdf/Cyrillic_good.pdf.
If this doesn't reproduce a valid pdf, it is a local problem and you should look at middleware or your OS or little green men, but not the code. I have this working locally with your file and no mangling is happening.
In fact, the only way to get a mangled pdf now is browser cache or response being modified after Django sends it, since the content length check would prevent sending a file that has different size then the one on disk.
JS Part
I would expect the conversion to happen in the blob constructor as it's possible to hand a blob a type. I'm not sure the default is binary-safe.
It's also weird your data has an error property and you pass the entire thing to the blob, but we can't see what promise you're reacting on.
success: function (data) {
if (data.error === undefined) {
console.log(data) // This will be informative
var blob = new Blob([data]);
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = filename + '.pdf';
link.click();
}
}
For those who are doing form validation in views, you need to add below code in js file as return type is expected as blob.
xhr: function() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 2) {
if (xhr.status == 200) {
xhr.responseType = "blob";
}
}
};
return xhr;
},
success: function (response, textStatus, jqXHR) {
var blob = new Blob([response])
var link=document.createElement('a');
link.href=window.URL.createObjectURL(blob);
link.download="contract.pdf";
link.click();
},
error: function (response, textStatus, jqXHR) {
$('#my_form').click();
}

How to serve a Python pickle file from a HTML page

I am trying to allow for downloading a Python pickle file from a Flask app through
import pickle
from flask import Flask, render_template_string
app = Flask(__name__)
template = """
<button onclick="download_file()" data-trigger-update-context="false">Download</button>
<script>
function download_file() {
mime_type = '{{ mime_type }}';
var blob = new Blob(['{{ file_content }}'], { type: mime_type });
var dlink = document.createElement('a');
dlink.download = 'pickle.pkl';
dlink.href = window.URL.createObjectURL(blob);
dlink.onclick = function (e) {
// revokeObjectURL needs a delay to work properly.
var that = this;
setTimeout(function () {
window.URL.revokeObjectURL(that.href);
}, 1500);
};
document.body.appendChild(dlink);
dlink.click();
dlink.remove();
}
</script>
"""
#app.route("/")
def download():
return render_template_string(
template,
file_content=pickle.dumps("text"),
mime_type="application/octet-stream",
)
While downloading the file works fine, the downloaded file itself seems corrupted as I get the following error while reading it
Python 3.7.6 | packaged by conda-forge | (default, Mar 23 2020, 23:03:20)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.14.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: import pickle
In [2]: with open("pickle.pkl", "rb") as f:
...: pickle.load(f)
...:
---------------------------------------------------------------------------
UnpicklingError Traceback (most recent call last)
<ipython-input-2-b5282f4164d8> in <module>
1 with open("pickle.pkl", "rb") as f:
----> 2 pickle.load(f)
3
UnpicklingError: unpickling stack underflow
Any hint on the issue with the download script?
Thanks for your help.
Basically, you need to change just two things:
In the download() method, you need to convert the serialized bytes to a list of Integers.
Then, you need to change the JavaScript code to read this list of numbers.
So, your code should look like this:
import pickle
from flask import Flask, render_template_string
app = Flask(__name__)
template = """
<button onclick="download_file()" data-trigger-update-context="false">Download</button>
<script>
function download_file() {
let bytes_array = new Uint8Array({{file_content}}); //<--- add this
mime_type = '{{ mime_type }}';
var blob = new Blob([bytes_array], { type: mime_type }); //<-- change this
var dlink = document.createElement('a');
dlink.download = 'pickle.pkl';
dlink.href = window.URL.createObjectURL(blob);
dlink.onclick = function (e) {
// revokeObjectURL needs a delay to work properly.
var that = this;
setTimeout(function () {
window.URL.revokeObjectURL(that.href);
}, 1500);
};
document.body.appendChild(dlink);
dlink.click();
dlink.remove();
}
</script>
"""
#app.route("/")
def download():
return render_template_string(
template,
file_content=list(pickle.dumps("text")), # change this
mime_type="application/octet-stream",
)
if __name__ == '__main__':
app.run(debug = True)
Now, you can read the pickled file using pickle.load() just like so:
import pickle
with open("pickle.pkl", 'rb') as fin:
print(pickle.load(fin))
# prints: text

Save texts from textarea as file and force browser to download [duplicate]

I have a javascript app that sends ajax POST requests to a certain URL. Response might be a JSON string or it might be a file (as an attachment). I can easily detect Content-Type and Content-Disposition in my ajax call, but once I detect that the response contains a file, how do I offer the client to download it? I've read a number of similar threads here but none of them provide the answer I'm looking for.
Please, please, please do not post answers suggesting that I shouldn't use ajax for this or that I should redirect the browser, because none of this is an option. Using a plain HTML form is also not an option. What I do need is to show a download dialog to the client. Can this be done and how?
Don't give up so quickly, because this can be done (in modern browsers) using parts of the FileAPI:
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.responseType = 'blob';
xhr.onload = function () {
if (this.status === 200) {
var blob = this.response;
var filename = "";
var disposition = xhr.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1) {
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, '');
}
if (typeof window.navigator.msSaveBlob !== 'undefined') {
// IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
window.navigator.msSaveBlob(blob, filename);
} else {
var URL = window.URL || window.webkitURL;
var downloadUrl = URL.createObjectURL(blob);
if (filename) {
// use HTML5 a[download] attribute to specify filename
var a = document.createElement("a");
// safari doesn't support this yet
if (typeof a.download === 'undefined') {
window.location.href = downloadUrl;
} else {
a.href = downloadUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
}
} else {
window.location.href = downloadUrl;
}
setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
}
}
};
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send($.param(params, true));
Or if using jQuery.ajax:
$.ajax({
type: "POST",
url: url,
data: params,
xhrFields: {
responseType: 'blob' // to avoid binary data being mangled on charset conversion
},
success: function(blob, status, xhr) {
// check for a filename
var filename = "";
var disposition = xhr.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1) {
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, '');
}
if (typeof window.navigator.msSaveBlob !== 'undefined') {
// IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
window.navigator.msSaveBlob(blob, filename);
} else {
var URL = window.URL || window.webkitURL;
var downloadUrl = URL.createObjectURL(blob);
if (filename) {
// use HTML5 a[download] attribute to specify filename
var a = document.createElement("a");
// safari doesn't support this yet
if (typeof a.download === 'undefined') {
window.location.href = downloadUrl;
} else {
a.href = downloadUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
}
} else {
window.location.href = downloadUrl;
}
setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
}
}
});
Create a form, use the POST method, submit the form - there's no need for an iframe. When the server page responds to the request, write a response header for the mime type of the file, and it will present a download dialog - I've done this a number of times.
You want content-type of application/download - just search for how to provide a download for whatever language you're using.
I faced the same issue and successfully solved it. My use-case is this.
"Post JSON data to the server and receive an excel file.
That excel file is created by the server and returned as a response to the client. Download that response as a file with custom name in browser"
$("#my-button").on("click", function(){
// Data to post
data = {
ids: [1, 2, 3, 4, 5]
};
// Use XMLHttpRequest instead of Jquery $ajax
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
var a;
if (xhttp.readyState === 4 && xhttp.status === 200) {
// Trick for making downloadable link
a = document.createElement('a');
a.href = window.URL.createObjectURL(xhttp.response);
// Give filename you wish to download
a.download = "test-file.xls";
a.style.display = 'none';
document.body.appendChild(a);
a.click();
}
};
// Post data to URL which handles post request
xhttp.open("POST", excelDownloadUrl);
xhttp.setRequestHeader("Content-Type", "application/json");
// You should set responseType as blob for binary responses
xhttp.responseType = 'blob';
xhttp.send(JSON.stringify(data));
});
The above snippet is just doing following
Posting an array as JSON to the server using XMLHttpRequest.
After fetching content as a blob(binary), we are creating a downloadable URL and attaching it to invisible "a" link then clicking it.
Here we need to carefully set few things at the server side. I set few headers in Python Django HttpResponse. You need to set them accordingly if you use other programming languages.
# In python django code
response = HttpResponse(file_content, content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
Since I download xls(excel) here, I adjusted contentType to above one. You need to set it according to your file type. You can use this technique to download any kind of files.
What server-side language are you using? In my app I can easily download a file from an AJAX call by setting the correct headers in PHP's response:
Setting headers server-side
header("HTTP/1.1 200 OK");
header("Pragma: public");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
// The optional second 'replace' parameter indicates whether the header
// should replace a previous similar header, or add a second header of
// the same type. By default it will replace, but if you pass in FALSE
// as the second argument you can force multiple headers of the same type.
header("Cache-Control: private", false);
header("Content-type: " . $mimeType);
// $strFileName is, of course, the filename of the file being downloaded.
// This won't have to be the same name as the actual file.
header("Content-Disposition: attachment; filename=\"{$strFileName}\"");
header("Content-Transfer-Encoding: binary");
header("Content-Length: " . mb_strlen($strFile));
// $strFile is a binary representation of the file that is being downloaded.
echo $strFile;
This will in fact 'redirect' the browser to this download page, but as #ahren alread said in his comment, it won't navigate away from the current page.
It's all about setting the correct headers so I'm sure you'll find a suitable solution for the server-side language you're using if it's not PHP.
Handling the response client side
Assuming you already know how to make an AJAX call, on the client side you execute an AJAX request to the server. The server then generates a link from where this file can be downloaded, e.g. the 'forward' URL where you want to point to.
For example, the server responds with:
{
status: 1, // ok
// unique one-time download token, not required of course
message: 'http://yourwebsite.com/getdownload/ska08912dsa'
}
When processing the response, you inject an iframe in your body and set the iframe's SRC to the URL you just received like this (using jQuery for the ease of this example):
$("body").append("<iframe src='" + data.message +
"' style='display: none;' ></iframe>");
If you've set the correct headers as shown above, the iframe will force a download dialog without navigating the browser away from the current page.
Note
Extra addition in relation to your question; I think it's best to always return JSON when requesting stuff with AJAX technology. After you've received the JSON response, you can then decide client-side what to do with it. Maybe, for example, later on you want the user to click a download link to the URL instead of forcing the download directly, in your current setup you would have to update both client and server-side to do so.
Here is how I got this working
https://stackoverflow.com/a/27563953/2845977
$.ajax({
url: '<URL_TO_FILE>',
success: function(data) {
var blob=new Blob([data]);
var link=document.createElement('a');
link.href=window.URL.createObjectURL(blob);
link.download="<FILENAME_TO_SAVE_WITH_EXTENSION>";
link.click();
}
});
Updated answer using download.js
$.ajax({
url: '<URL_TO_FILE>',
success: download.bind(true, "<FILENAME_TO_SAVE_WITH_EXTENSION>", "<FILE_MIME_TYPE>")
});
For those looking for a solution from an Angular perspective, this worked for me:
$http.post(
'url',
{},
{responseType: 'arraybuffer'}
).then(function (response) {
var headers = response.headers();
var blob = new Blob([response.data],{type:headers['content-type']});
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = "Filename";
link.click();
});
For those looking for a more modern approach, you can use the fetch API. The following code shows how to download a spreadsheet file.
fetch(url, {
body: JSON.stringify(data),
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
})
.then(response => response.blob())
.then(response => {
const blob = new Blob([response], {type: 'application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
const downloadUrl = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = downloadUrl;
a.download = "file.xlsx";
document.body.appendChild(a);
a.click();
})
I believe this approach to be much easier to understand than other XMLHttpRequest solutions. Also, it has a similar syntax to the jQuery approach, without the need to add any additional libraries.
Of course, I would advise checking to which browser you are developing, since this new approach won't work on IE. You can find the full browser compatibility list on the following [link][1].
Important: In this example I am sending a JSON request to a server listening on the given url. This url must be set, on my example I am assuming you know this part. Also, consider the headers needed for your request to work. Since I am sending a JSON, I must add the Content-Type header and set it to application/json; charset=utf-8, as to let the server know the type of request it will receive.
I see you've already found out a solution, however I just wanted to add some information which may help someone trying to achieve the same thing with big POST requests.
I had the same issue a couple of weeks ago, indeed it isn't possible to achieve a "clean" download through AJAX, the Filament Group created a jQuery plugin which works exactly how you've already found out, it is called jQuery File Download however there is a downside to this technique.
If you're sending big requests through AJAX (say files +1MB) it will negatively impact responsiveness. In slow Internet connections you'll have to wait a lot until the request is sent and also wait for the file to download. It isn't like an instant "click" => "popup" => "download start". It's more like "click" => "wait until data is sent" => "wait for response" => "download start" which makes it appear the file double its size because you'll have to wait for the request to be sent through AJAX and get it back as a downloadable file.
If you're working with small file sizes <1MB you won't notice this. But as I discovered in my own app, for bigger file sizes it is almost unbearable.
My app allow users to export images dynamically generated, these images are sent through POST requests in base64 format to the server (it is the only possible way), then processed and sent back to users in form of .png, .jpg files, base64 strings for images +1MB are huge, this force users to wait more than necessary for the file to start downloading. In slow Internet connections it can be really annoying.
My solution for this was to temporary write the file to the server, once it is ready, dynamically generate a link to the file in form of a button which changes between "Please wait..." and "Download" states and at the same time, print the base64 image in a preview popup window so users can "right-click" and save it. This makes all the waiting time more bearable for users, and also speed things up.
Update Sep 30, 2014:
Months have passed since I posted this, finally I've found a better approach to speed things up when working with big base64 strings. I now store base64 strings into the database (using longtext or longblog fields), then I pass its record ID through the jQuery File Download, finally on the download script file I query the database using this ID to pull the base64 string and pass it through the download function.
Download Script Example:
<?php
// Record ID
$downloadID = (int)$_POST['id'];
// Query Data (this example uses CodeIgniter)
$data = $CI->MyQueries->GetDownload( $downloadID );
// base64 tags are replaced by [removed], so we strip them out
$base64 = base64_decode( preg_replace('#\[removed\]#', '', $data[0]->image) );
// This example is for base64 images
$imgsize = getimagesize( $base64 );
// Set content headers
header('Content-Disposition: attachment; filename="my-file.png"');
header('Content-type: '.$imgsize['mime']);
// Force download
echo $base64;
?>
I know this is way beyond what the OP asked, however I felt it would be good to update my answer with my findings. When I was searching for solutions to my problem, I read lots of "Download from AJAX POST data" threads which didn't give me the answer I was looking for, I hope this information helps someone looking to achieve something like this.
Here is my solution using a temporary hidden form.
//Create an hidden form
var form = $('<form>', {'method': 'POST', 'action': this.href}).hide();
//Add params
var params = { ...your params... };
$.each(params, function (k, v) {
form.append($('<input>', {'type': 'hidden', 'name': k, 'value': v}));
});
//Make it part of the document and submit
$('body').append(form);
form.submit();
//Clean up
form.remove();
Note that I massively use JQuery but you can do the same with native JS.
I want to point out some difficulties that arise when using the technique in the accepted answer, i.e. using a form post:
You can't set headers on the request. If your authentication schema involves headers, a Json-Web-Token passed in the Authorization header, you'll have to find other way to send it, for example as a query parameter.
You can't really tell when the request has finished. Well, you can use a cookie that gets set on response, as done by jquery.fileDownload, but it's FAR from perfect. It won't work for concurrent requests and it will break if a response never arrives.
If the server responds with a error, the user will be redirected to the error page.
You can only use the content types supported by a form. Which means you can't use JSON.
I ended up using the method of saving the file on S3 and sending a pre-signed URL to get the file.
As others have stated, you can create and submit a form to download via a POST request. However, you don't have to do this manually.
One really simple library for doing exactly this is jquery.redirect. It provides an API similar to the standard jQuery.post method:
$.redirect(url, [values, [method, [target]]])
This is a 3 years old question but I had the same problem today. I looked your edited solution but I think that it can sacrifice the performance because it has to make a double request. So if anyone needs another solution that doesn't imply to call the service twice then this is the way I did it:
<form id="export-csv-form" method="POST" action="/the/path/to/file">
<input type="hidden" name="anyValueToPassTheServer" value="">
</form>
This form is just used to call the service and avoid to use a window.location(). After that you just simply have to make a form submit from jquery in order to call the service and get the file. It's pretty simple but this way you can make a download using a POST. I now that this could be easier if the service you're calling is a GET, but that's not my case.
I used this FileSaver.js. In my case with csv files, i did this (in coffescript):
$.ajax
url: "url-to-server"
data: "data-to-send"
success: (csvData)->
blob = new Blob([csvData], { type: 'text/csv' })
saveAs(blob, "filename.csv")
I think for most complicated case, the data must be processed properly. Under the hood FileSaver.js implement the same approach of the answer of Jonathan Amend.
see: http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/
it'll return a blob as a response, which can then be put into filesaver
Here is my solution, gathered from different sources:
Server side implementation :
String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
// Set headers
response.setHeader("content-disposition", "attachment; filename =" + fileName);
response.setContentType(contentType);
// Copy file to output stream
ServletOutputStream servletOutputStream = response.getOutputStream();
try (InputStream inputStream = new FileInputStream(file)) {
IOUtils.copy(inputStream, servletOutputStream);
} finally {
servletOutputStream.flush();
Utils.closeQuitely(servletOutputStream);
fileToDownload = null;
}
Client side implementation (using jquery):
$.ajax({
type: 'POST',
contentType: 'application/json',
url: <download file url>,
data: JSON.stringify(postObject),
error: function(XMLHttpRequest, textStatus, errorThrown) {
alert(errorThrown);
},
success: function(message, textStatus, response) {
var header = response.getResponseHeader('Content-Disposition');
var fileName = header.split("=")[1];
var blob = new Blob([message]);
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = fileName;
link.click();
}
});
Below is my solution for downloading multiple files depending on some list which consists of some ids and looking up in database, files will be determined and ready for download - if those exist.
I am calling C# MVC action for each file using Ajax.
And Yes, like others said, it is possible to do it in jQuery Ajax.
I did it with Ajax success and I am always sending response 200.
So, this is the key:
success: function (data, textStatus, xhr) {
And this is my code:
var i = 0;
var max = 0;
function DownloadMultipleFiles() {
if ($(".dataTables_scrollBody>tr.selected").length > 0) {
var list = [];
showPreloader();
$(".dataTables_scrollBody>tr.selected").each(function (e) {
var element = $(this);
var orderid = element.data("orderid");
var iscustom = element.data("iscustom");
var orderlineid = element.data("orderlineid");
var folderPath = "";
var fileName = "";
list.push({ orderId: orderid, isCustomOrderLine: iscustom, orderLineId: orderlineid, folderPath: folderPath, fileName: fileName });
});
i = 0;
max = list.length;
DownloadFile(list);
}
}
Then calling:
function DownloadFile(list) {
$.ajax({
url: '#Url.Action("OpenFile","OrderLines")',
type: "post",
data: list[i],
xhrFields: {
responseType: 'blob'
},
beforeSend: function (xhr) {
xhr.setRequestHeader("RequestVerificationToken",
$('input:hidden[name="__RequestVerificationToken"]').val());
},
success: function (data, textStatus, xhr) {
// check for a filename
var filename = "";
var disposition = xhr.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1) {
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, '');
var a = document.createElement('a');
var url = window.URL.createObjectURL(data);
a.href = url;
a.download = filename;
document.body.append(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
}
else {
getErrorToastMessage("Production file for order line " + list[i].orderLineId + " does not exist");
}
i = i + 1;
if (i < max) {
DownloadFile(list);
}
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
},
complete: function () {
if(i===max)
hidePreloader();
}
});
}
C# MVC:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult OpenFile(OrderLineSimpleModel model)
{
byte[] file = null;
try
{
if (model != null)
{
//code for getting file from api - part is missing here as not important for this example
file = apiHandler.Get<byte[]>(downloadApiUrl, token);
var contentDispositionHeader = new System.Net.Mime.ContentDisposition
{
Inline = true,
FileName = fileName
};
// Response.Headers.Add("Content-Disposition", contentDispositionHeader.ToString() + "; attachment");
Response.Headers.Add("Content-Type", "application/pdf");
Response.Headers.Add("Content-Disposition", "attachment; filename=" + fileName);
Response.Headers.Add("Content-Transfer-Encoding", "binary");
Response.Headers.Add("Content-Length", file.Length.ToString());
}
}
catch (Exception ex)
{
this.logger.LogError(ex, "Error getting pdf", null);
return Ok();
}
return File(file, System.Net.Mime.MediaTypeNames.Application.Pdf);
}
As long as you return response 200, success in Ajax can work with it, you can check if file actually exist or not as the line below in this case would be false and you can inform user about that:
if (disposition && disposition.indexOf('attachment') !== -1) {
To get Jonathan Amends answer to work in Edge I made the following changes:
var blob = typeof File === 'function'
? new File([this.response], filename, { type: type })
: new Blob([this.response], { type: type });
to this
var f = typeof File+"";
var blob = f === 'function' && Modernizr.fileapi
? new File([this.response], filename, { type: type })
: new Blob([this.response], { type: type });
I would rather have posted this as a comment but I don't have enough reputation for that
there is another solution to download a web page in ajax. But I am referring to a page that must first be processed and then downloaded.
First you need to separate the page processing from the results download.
1) Only the page calculations are made in the ajax call.
$.post("CalculusPage.php", { calculusFunction: true, ID: 29, data1: "a", data2: "b" },
function(data, status)
{
if (status == "success")
{
/* 2) In the answer the page that uses the previous calculations is downloaded. For example, this can be a page that prints the results of a table calculated in the ajax call. */
window.location.href = DownloadPage.php+"?ID="+29;
}
}
);
// For example: in the CalculusPage.php
if ( !empty($_POST["calculusFunction"]) )
{
$ID = $_POST["ID"];
$query = "INSERT INTO ExamplePage (data1, data2) VALUES ('".$_POST["data1"]."', '".$_POST["data2"]."') WHERE id = ".$ID;
...
}
// For example: in the DownloadPage.php
$ID = $_GET["ID"];
$sede = "SELECT * FROM ExamplePage WHERE id = ".$ID;
...
$filename="Export_Data.xls";
header("Content-Type: application/vnd.ms-excel");
header("Content-Disposition: inline; filename=$filename");
...
I hope this solution can be useful for many, as it was for me.
If response is an Array Buffer, try this under onsuccess event in Ajax:
if (event.data instanceof ArrayBuffer) {
var binary = '';
var bytes = new Uint8Array(event.data);
for (var i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i])
}
$("#some_id").append("<li><img src=\"data:image/png;base64," + window.btoa(binary) + "\"/></span></li>");
return;
}
where event.data is response received in success function of xhr event.
I needed a similar solution to #alain-cruz's one, but in nuxt/vue with multiple downloads. I know browsers block multiple file downloads, and I also have API which returns a set of csv formatted data.I was going to use JSZip at first but I needed IE support so here is my solution. If anyone can help me improve this that would be great, but it's working for me so far.
API returns:
data : {
body: {
fileOne: ""col1", "col2", "datarow1.1", "datarow1.2"...so on",
fileTwo: ""col1", "col2"..."
}
}
page.vue:
<template>
<b-link #click.prevent="handleFileExport">Export<b-link>
</template>
export default = {
data() {
return {
fileNames: ['fileOne', 'fileTwo'],
}
},
computed: {
...mapState({
fileOne: (state) => state.exportFile.fileOne,
fileTwo: (state) => state.exportFile.fileTwo,
}),
},
method: {
handleExport() {
//exportFileAction in store/exportFile needs to return promise
this.$store.dispatch('exportFile/exportFileAction', paramsToSend)
.then(async (response) => {
const downloadPrep = this.fileNames.map(async (fileName) => {
// using lodash to get computed data by the file name
const currentData = await _.get(this, `${fileName}`);
const currentFileName = fileName;
return { currentData, currentFileName };
});
const response = await Promise.all(downloadPrep);
return response;
})
.then(async (data) => {
data.forEach(({ currentData, currentFileName }) => {
this.forceFileDownload(currentData, currentFileName);
});
})
.catch(console.error);
},
forceFileDownload(data, fileName) {
const url = window.URL
.createObjectURL(new Blob([data], { type: 'text/csv;charset=utf-8;' }));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', `${fileName}.csv`);
document.body.appendChild(link);
link.click();
},
}
I used Naren Yellavula's solution and got it working with few changes to the script, after trying several other solutions using jquery. But, jquery will not download a zip file properly. I can't unzip the file after download.
In my use case, I have to upload a zip file, which is unzipped in the Servlet, files are processed and zipped again before the zip file is downloaded to the client. This is what you need to do on client side.
$('#fileUpBtn').click(function (e){
e.preventDefault();
var file = $('#fileUpload')[0].files[0];
var formdata = new FormData();
formdata.append('file', file);
// Use XMLHttpRequest instead of Jquery $ajax to download zip files
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (xhttp.readyState === 4 && xhttp.status === 200) {
var a = document.createElement('a');
a.href = window.URL.createObjectURL(xhttp.response);
a.download = "modified_" + file.name;
a.style.display = 'none';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(a.href);
}
};
xhttp.open("POST", "<URL to Servlet>", true);
xhttp.responseType = 'blob';
xhttp.send(formdata);
});
<div class="form-group">
<label id="fileUpLabel" for="fileUpload"></label>
<input type="file" class="form-control" id="fileUpload" name="file" accept="" required/>
</div>
<button class="btn" type="submit" id="fileUpBtn"></button>

How to get file name from content-disposition

I downloaded a file as response of ajax. How to get the file name and file type from content-disposition and display thumbnail for it. I got many search results but couldn't find right way.
$(".download_btn").click(function () {
var uiid = $(this).data("id2");
$.ajax({
url: "http://localhost:8080/prj/" + data + "/" + uiid + "/getfile",
type: "GET",
error: function (jqXHR, textStatus, errorThrown) {
console.log(textStatus, errorThrown);
},
success: function (response, status, xhr) {
var header = xhr.getResponseHeader('Content-Disposition');
console.log(header);
}
});
Console output:
inline; filename=demo3.png
Here is how I used it sometime back.
I'm assuming you are providing the attachment as a server response.
I set the response header like this from my REST service response.setHeader("Content-Disposition", "attachment;filename=XYZ.csv");
function(response, status, xhr){
var filename = "";
var disposition = xhr.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1) {
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) {
filename = matches[1].replace(/['"]/g, '');
}
}
}
EDIT:
Editing the answer to suit your question- use of the word inline instead of attachment
function(response, status, xhr){
var filename = "";
var disposition = xhr.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('inline') !== -1) {
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) {
filename = matches[1].replace(/['"]/g, '');
}
}
}
More here
This is an improvement on marjon4's answer.
A much simplified way to the selected answer would be to use split like this;
var fileName = xhr.getResponseHeader('content-disposition').split('filename=')[1].split(';')[0];
Note: This solution may not work as expected if your file name itself contains a semi-colon (;)
If you want to get the filename and support both those weird url encoded UTF-8 headers and the ascii headers, you can use something like this
public getFileName(disposition: string): string {
const utf8FilenameRegex = /filename\*=UTF-8''([\w%\-\.]+)(?:; ?|$)/i;
const asciiFilenameRegex = /^filename=(["']?)(.*?[^\\])\1(?:; ?|$)/i;
let fileName: string = null;
if (utf8FilenameRegex.test(disposition)) {
fileName = decodeURIComponent(utf8FilenameRegex.exec(disposition)[1]);
} else {
// prevent ReDos attacks by anchoring the ascii regex to string start and
// slicing off everything before 'filename='
const filenameStart = disposition.toLowerCase().indexOf('filename=');
if (filenameStart >= 0) {
const partialDisposition = disposition.slice(filenameStart);
const matches = asciiFilenameRegex.exec(partialDisposition );
if (matches != null && matches[2]) {
fileName = matches[2];
}
}
}
return fileName;
}
A couple of notes:
this will take the value of the UTF-8 filename, if set, over the ascii name
on download, your browser may further alter the name to replace certain characters, like ", with _ (Chrome)
the ascii pattern works best for quoted file names, but supports unquoted values. In that case it treats all text after the filename= and before the either the next ; or the end of the header value as the file name.
This does not clean up path information. If you are saving the file from a website, that's the browser's job, but if your using this in the context of a node app or something similar, be sure to clean up the path information per the OS and leave just the filename, or a crafted file name might be used to overwrite a system file (think of a file name like ../../../../../../../path/to/system/files/malicious.dll)
MDN Content Disposition Header
Or simply just:
var fileName = xhr.getResponseHeader('Content-Disposition').split("filename=")[1];
In my case the header looks like this:
attachment; filename="test-file3.txt"
Therefore I was able to extract the filename pretty easily with a named group regexp:
const regExpFilename = /filename="(?<filename>.*)"/;
const filename: string | null = regExpFilename.exec(contentDispositionHeader)?.groups?.filename ?? null;
I know I'm slightly off topic here as OP doesn't have the quotes around the filename but still sharing in case someone comes across the same pattern as I just did
Try this solution:
var contentDisposition = xhr.getResponseHeader('Content-Disposition');
var startIndex = contentDisposition.indexOf("filename=") + 10; // Adjust '+ 10' if filename is not the right one.
var endIndex = contentDisposition.length - 1; //Check if '- 1' is necessary
var filename = contentDisposition.substring(startIndex, endIndex);
console.log("filename: " + filename)
There is an npm package that does the job: content-disposition
I believe this will help!
let filename = response.headers['content-disposition'].split('filename=')[1].split('.')[0];
let extension = response.headers['content-disposition'].split('.')[1].split(';')[0];
There's also library content-disposition-attachment, which can be used in the browser:
npm i -D content-disposition-attachment
import { AxiosResponse } from "axios";
import { parse } from "content-disposition-attachment";
const getFilenameFromHeaders = ({ headers }: AxiosResponse<Blob>) => {
const defaultName = "untitled";
try {
const { attachment, filename } = parse(headers["content-disposition"]);
return attachment ? filename : defaultName;
} catch (e) {
console.error(e);
return defaultName;
}
};
The below also takes into account scenarios where the filename includes unicode characters (i.e.,-, !, (, ), etc.) and hence, comes (utf-8 encoded) in the form of, for instance, filename*=utf-8''Na%C3%AFve%20file.txt (see here for more details). In such cases, the decodeURIComponent() function is used to decode the filename.
const disposition = xhr.getResponseHeader('Content-Disposition');
filename = disposition.split(/;(.+)/)[1].split(/=(.+)/)[1]
if (filename.toLowerCase().startsWith("utf-8''"))
filename = decodeURIComponent(filename.replace("utf-8''", ''))
else
filename = filename.replace(/['"]/g, '')
If you are doing a cross-origin request, make sure to add Access-Control-Expose-Headers: Content-Disposition to the response headers on server side (see Access-Control-Expose-Headers), in order to expose the Content-Disposition header; otherwise, the filename won't be accessible on client side through JavaScript. For instance:
headers = {'Access-Control-Expose-Headers': 'Content-Disposition'}
return FileResponse("Naïve file.txt", filename="Naïve file.txt", headers=headers)
If you are not working with multipart body then you can use this function. It extracts the filename from the Content-Disposition header value (string like: inline; filename=demo3.png) and decodes as needed.
const getFileNameFromContentDisposition = disposition => {
if (disposition
&& (disposition.startsWith('attachment') || disposition.startsWith('inline'))
) {
let filename = disposition.startsWith('attachment')
? disposition.replace("attachment;", "")
: disposition.replace("inline;", ""); //replaces first match only
filename = filename.trim();
if (filename.includes("filename*=") && filename.includes("filename=")) {
let filenames = filename.split(";"); //we can parse by ";" because all ";"s inside filename are escaped
if (filenames.length > 1) { //"filename=" or "filename*=" not inside filename
if (filenames[0].trim().startsWith("filename*=")) { //"filename*=" is preferred
filename = filenames[0].trim();
} else {
filename = filenames[1].trim();
}
}
}
if (filename.startsWith("filename*=")) {
filename = filename.replace("filename*=", "")
.split("''").slice(1).join("''"); //remove encoding and ''
filename = decodeURIComponent(filename);
} else if (filename.startsWith("filename=")) {
filename = filename.replace("filename=", "")
if (filename.startsWith('"') && filename.endsWith('"')) {
filename = filename.slice(1, filename.length - 1); //remove quotes
}
}
return filename;
}
}
The result of the function can be split into name and extension as follows:
let name = getFileNameFromContentDisposition("inline; filename=demo.3.png").split(".");
let extension = name[name.length - 1];
name = name.slice(0, name.length - 1).join(".");
console.log(name); // demo.3
console.log(extension); //png
You can display thumbnail, for example, using svg:
let colors = {"png": "red", "jpg": "orange"};
//this is a simple example, you can make something more beautiful
let createSVGThumbnail = extension => `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" viewBox="0 0 18 20">
<rect x="0" y="0" width="18" height="20" fill = "#FAFEFF"/>
<rect x="0" y="7" width="18" height="6" stroke="${colors[extension] || "blue"}" fill = "${colors[extension] || "blue"}"/>
<text stroke = "white" fill = "white" font-size = "6" x = "0" y = "12.5" textLength = "18">${extension.toUpperCase()}</text>
</svg>`;
...
//You can use it as HTML element background-image
let background = "data:image/svg+xml;base64," + btoa(new TextDecoder().decode(createSVGThumbnail("png")));

Categories

Resources