downloaded data file from ajax - javascript

I am trying to initiate a file download through ajax. I can retrieve the data from the server, but cannot get the browser to open the data. I can't just point the browser's location.href at the endpoint url.
the resource I want to download is being exposed through an endpoint that requires custom http headers, and an authentication bearer token. I cannot change the backend api to allow cookies. Therefore, I cannot just open the url with window.open(url,'_blank')
I can make an ajax request to the endpoint, but I don't know how to download file after I get the response.
$.get( "restAPI/file.pdf", function( data ) {
var w = window.open(null,'_blank')
$(w.document.body).html(data);
});
Does not work either
I was hoping to do something similar to
var w = window.open(data,'_blank')
but that does not work either.
EDIT
The solution, thanks to joyBlanks
$http({method: 'GET',
responseType:'arraybuffer',
headers: {
Accept: 'application/octet-stream',
}, url:url }
).then(function(data) {
var blob = new Blob([data.data]);
if (window.navigator.msSaveBlob)
window.navigator.msSaveBlob(blob, filename);
else {
var link = document.createElement('a');
link.id = filename;
link.href = window.URL.createObjectURL(blob);
link.download = filename;
link.click();
}
});

Modern browsers support the download attribute for the <a> tag.
This attribute, if present, indicates that the author intends the hyperlink to be used for downloading a resource so that when the user clicks on the link they will be prompted to save it as a local file. If the attribute has a value, the value will be used as the pre-filled file name in the Save prompt that opens when the user clicks on the link (the user can change the name before actually saving the file of course). There are no restrictions on allowed values (though / and \ will be converted to underscores, preventing specific path hints), but you should consider that most file systems have limitations with regard to what punctuation is supported in file names, and browsers are likely to adjust file names accordingly.
Note:
Can be used with blob: URLs and data: URLs, to make it easy for users to download content that is generated programmatically using JavaScript (e.g. a picture created using an online drawing Web app).
If the HTTP header Content-Disposition: is present and gives a different filename than this attribute, the HTTP header has priority over this attribute.
If this attribute is present and Content-Disposition: is set to inline, Firefox gives priority to Content-Disposition, like for the filename case, while Chrome gives priority to the download attribute.
This attribute is only honored for links to resources with the same-origin.
<a download src="restAPI/file.pdf">Download File</a>
So when you click the a tag it will show a popup that will download the file. From the request I can see that the file is already available.
You can read about it more : https://developer.mozilla.org/en/docs/Web/HTML/Element/a

You won't be able to save the file from javascript. I would recommend you create an an API call that calls the restAPI and saves a temp file on your webserver. Then return the temp file name to the javascript and redirect to it. The browser should them prompt the user to open or save.
Here is another post that has more details on this approach:
Web Api won't download file using jQuery Ajax and Basic Auth

Related

Get filename of downloadable binary from php url without actually downloading the file

I'm doing some webscraping with Selenium in Python and I have a link on a page that points to, e.g.
Click Here To Download
Now, of course, if I click on it, my browser will immediately start downloading the file, e.g. myinterestingarchive.zip
What I'm wondering is if I can inject some JavaScript, say, that will tell me the filename myinterestingarchive.zip WITHOUT my clicking the link, because I'd like to record the filename in my program's log, and it's nowhere in the source or OuterHTML, just that php url.
If it support HEAD request that will only download http headers you can do
import requests
......
# set the request with selenium cookies
cookies = {c['name']: c['value'] for c in driver.get_cookies()}
response = requests.head('http://....../zip.php?zipid=103', cookies=cookies )
print(response.headers['Content-Disposition'])
# attachment; filename=zip/myinterestingarchive.zip
And yes you can do this with injected JavaScript but it more simple using requests

Upload images to S3 doesn't work

I'm trying to upload files to S3 without having to send to my server. I've a endpoint which gives me signed S3 URL where I can make PUT requests to store files to my bucket.
I tried to do couple of things on JavaScript side which didn't work. (I'm not using amazon's SDK, and prefer not to, because I'm looking for simple file upload and nothing more than that)
Here's what I'm trying to do currently in JavaScript:
uploadToS3 = () => {
let file = this.state.files[0];
let formData = new FormData();
formData.append('Content-Type', file.type);
formData.append('file', file);
let xhr = new XMLHttpRequest();
xhr.open('put', this.signed_url, true);
xhr.send(formData)
};
I tried bunch of options, I prefer using fetch because I don't really care for upload progress since these are just images. I used xhr code from somewhere to try out like above. These do make network calls and seem like they should work but they don't.
Here's what happens: An object is created on S3, when I go to public URL, they get downloaded and when I use image viewer to open them, they say it's not valid JPG.
I'm thinking I'm not doing the upload correctly.
Here's how I do in postman:
Notice I have correct signed URL and I've attached binary image file to the request. And added a header stating content type is image/jpeg as shown below:
When I login to S3 and go to my bucket, I can see an image and I can go to it's public URL and view in browser. This works perfect and is exactly what I want, now I don't know how I could achieve the same on JavaScript.
PS: I even tried to click on code on postman, it doesn't generate file code for me.
The problem here starts with xhr.send(formData).
When you PUT a file in S3 you don't use any form structures at all, you just send the raw object bytes in the request body.
Content-Type: and other metadata goes in the request headers, not in form data in the body.
In this case, if you download your uploaded file and view it with a text editor, the problem should be very apparent once you see what your code is actually sending to S3, which S3 then obediently stores and serves up on subsequent requests.
Note that S3 does have support for browser-based form POST uploads, but when doing so the signing process is significantly different, requiring you to create and sign a policy document, so that you can send the form, including the policy and signature, to the browser and allow an otherwise-untrusted user to upload a file -- the signed policy statement prevents the browser user from tampering with the form and performing actions that you didn't intend.

Change file name when using window.location to download

I'm using window.location to download my image. It isn't in HTML because I generate the image on the server and then send it back down so it looks like :
window.location = data.url;
I've seen a few other questions but they suggest the download attr which I don't have because there's no HTML.
Is there a way I can change the file name?
Front-end solution
The only thing you can do on the front-end side is to change your code to HTML <a> element with download attribute:
Download
When user clicks this link, the browser forces download and saves the file with given filename. You can read more about it in this post. It's quite a new feature so check the browser support.
Back-end solution
If you can modify the server-side code then you should use content-disposition header as defined in RFC 2183.
content-disposition: attachment; filename=very_important_report.pdf
I've been wondering about it as well and saw this post but I was also using vuejs for the project and want the export to be continues even when switching from one page to another so I tried something and it did work here is another solution:
var link = document.createElement('a');
link.setAttribute('href', '<yourlink_or_data>');
link.setAttribute('download', 'filename.ext');
link.click();
You can't change the filename on the client side. You would have to do that on the server.
You could set the content-disposition header (on the server side) like this:
Content-Disposition: attachment; filename="yourname.gif"

Intercept GET request and redirect to generated file

I'm building a chrome extension and using chrome.webRequest to intercept all GET requests. One of the request gets an XML file from http://test.com/bla.xml. This XML file is hosted on my webserver, so getting it is straight forward. The question I have is, how can I send a generated XML file and return it. I know how to intercept and modify the URL in the webRequest, but I'm not quite sure how to send it to a file I create on the fly.
chrome.webRequest.onBeforeRequest.addListener(
function (details) {
if (details.url === 'http://test.com/bla.xml') {
var xmlDoc = "<person>me</person>";
return { redirectUrl: xmlDoc };
}
I want to be able to redirect the XHR to a dynamically generated file. This file isn't necessarily an XML document, it could be a jpg file that I create with a stored blob.
Update: I have tried to forward to a data URI and now get this error:
XMLHttpRequest cannot load http://test.com/bla.xml. The request was redirected to a URL ('data:text/xml;base64,sdfkldsfjdslfjsfsjf') which has a disallowed scheme for cross-origin requests.
I have added http:///, and data: to the manifest.
The most straightforward way is to dynamically generate the file you want to return on your server.
Barring that, have you tried a data: URI? Given that you can control the redirect URL, you can encode the content you want to return in a data: URI and return that as the redirect target. See below for reference:
https://developer.mozilla.org/en-US/docs/Web/HTTP/data_URIs

Make a JSON POST request to server, receive a binary response (an Excel file), how to download it?

I'm trying to to make a POST call to server that sent JSON data to it. The server takes the JSON data, do some processing, then send back an Excel .xlsx as the response. I want the browser to open the "Save file as" dialog for the user to save. I have been looking for a clean solution to do this. But one possible solution in this question JavaScript/jQuery to download file via POST with JSON data suggest to save the Excel file on the server then send back a URL link, then open an iframe for user to download. This is a no-go for me, as the users can create thousands Excel files on the server and the server has limited saving spaces. I want the solution to be on-the-fly. Another solution I have seen suggested to convert data into form, then using form submit. Again this is a no-go, since my data is in the range of hundreds if not thousands of Excel rows.
My jQuery POST call:
$.ajax({
type: 'POST',
url: '/server/path',
data: JSON.stringify(dataSent),
processData: false,
success: function(data, textStatus, jqXHR) {
},
error: function(result, status, err) {
},
contentType: 'application/json',
dataType: 'application/vnd.ms-excel'
});
In the backend I set this :
Response.header("Content-Type", "application/vnd.ms-excel")
Response.header("Content-Disposition", "attachment; filename=\"export.xlsx\"")
What the best way to force the browser to open "Save file as ..." dialog ?
Thanks,
I'm not sure there's a way to recieve binary data via JS and then initiate the download.
If I were tasked with this, I would change the method to a GET and generate the file (as a stream) and return it with the appropriate headers (Content-Disposition, Content-Length, Content-Type)
I figure out a way around this. Instead of making a POST call to force the browser to open the save dialog, I will make a POST call to generate the file, then temporary store the file on the server, return the filename . Then use a GET call for this file with "Content-Disposition: attachment; filename=filename1". The GET call with that header will force the browser to open the "Save this file" dialog, always.
This is actually very easy with Blob URLs.
First, download the file. I'll use fetch with async/await in TypeScript (you can always use promise chains instead of async/await and/or XHR instead of fetch):
(async () => {
let response = await fetch("/post/path", {
body: JSON.stringify(data), // must match 'Content-Type' header
headers: {
'content-type': 'application/json'
},
method: 'POST',
});
let blob = await response.blob();
let filename = "file.txt";
saveBlobAsFile(filename, blob); // This function is defined below
})();
Now that you have a blob, you can pass it to a function to download it by creating a Blob URL and a hidden link:
/**
* Downloads a blob as a file.
*
* TODO: Support iOS Safari, which doesn't support the "download" attribute.
*
* #param name The name of the downloaded file
* #param blob The blob to download
*/
export function saveBlobAsFile(name: string, blob: Blob) {
// IE10 & IE11 Support, since they don't support the "download"
// attribute on anchor tags.
if (navigator.msSaveBlob) {
navigator.msSaveBlob(blob, name);
return;
}
// Create the URL and hidden anchor tag
let $hiddenAnchorTag = $('<a style="display: none;"/>');
let url = URL.createObjectURL(blob);
// Set the attributes for the download
$hiddenAnchorTag.attr('href', url);
$hiddenAnchorTag.attr('download', name);
// Insert the link and click to download
$(document.body).append($hiddenAnchorTag);
$hiddenAnchorTag[0].click();
// Clean up after ourselves
$hiddenAnchorTag.remove();
URL.revokeObjectURL(url);
}
Other Notes
The fetch response object contains the headers, so you can parse the Content-Disposition to get the filename intended by the server. I found a couple good Regexes around the web that work pretty well for this. Mileage may vary, but I recommend making a function for this and bounding it with some nice unit tests.
This works a lot better than trying to set the current location to the location of the file, because it allows you to include more details in the POST, including API keys or something similar for security, plus it allows you to handle errors/exceptions cleanly, and know when the operation is complete (such as warning on trying to navigate away from the page that the user is still waiting on a download).
Blobs even support slicing in data, so you could extend this to download large files by fetching the individual slices (yay Content-Range!) and assembling them into a single Blob, and downloading the final blob, all while giving the user a nice loading progress indicator!
You can use Blob URLs just like any other URLs. They point to the resource, so you could pass that URL to img tags, other libraries that ask for URLs, src tags, etc.
#phamductri Serving up temporary files on the server can be very dangerous! If you need to use that pattern, you'll want to abstract the filename using a table or lookup, so the user doesn't have control over the actual filenames or paths (use UUIDs in a specified directory), and make sure users can only download the files they generated. Just a few of things you need to ensure are as follows (this is not a comprehensive list):
Users can't specify an arbitrary path to save to
They could save over your database configuration file, etc.
Users can't specify an arbitrary path to read to
They could read your database configuration file, etc.
File names can't conflict.
User A generates a file with the name "accounts_to_pay.csv". User B generates a file at the same time with the same name (either maliciously or accidentally), and now User A is paying whoever User B wants them to.

Categories