8

I have a PDF file that downloads across all browsers except for Firefox/Safari. It opens it in the browser window instead of download of the file. My site is running on top of node.js and hosted in Azure. The file users download is coming from Azure blob storage, so I suspect this may be a CORS issue.

Here is my client side code for the download:

<a href="{{fileURL}}" class="btn btn-default" download="myfile.pdf">Download File</a>

Here is the server side code:

$scope.fileURL = 'https://myblob.blob.core.windows.net/8282020/myfile.pdf';

Update: I am able to add the content disposition by setting the blob properties in Azure per the help below, and it is showing the disposition as "attachment" but in FireFox/Safari it is still opening in the browser. Is this possibly being blocked since Azure Blob storage might be viewed as CORS?

Update 2: Adding the following to my HTML tag seems to work in FireFox (but not Safari), what is this the right way to handle this across Browsers

type="application/octet-stream"

Update 3: Setting the content disposition and content type via Node appears to be working. I have to ask, is this the right way?

blobSvc.setBlobProperties(containerName, filename, { contentDisposition: 'attachment', contentType: 'application/octet-stream' }, function (error, result, response) {
// result code here....
})
sideshowbarker
  • 81,827
  • 26
  • 193
  • 197
Kode
  • 3,073
  • 18
  • 74
  • 140

5 Answers5

3

Generally, we can set the Content-Disposition of blob to attachment to indicate that the user-agent should not display the response, but instead show a Save As dialog with a filename other than the blob name specified. You can refer to Set Blob Properties for more information.

Can I set the disposition on initial upload? I am using Node/JavaScript: blobSvc.createBlockBlobFromLocalFile('mycontainer', 'myblob', 'test.txt', function(error, result, response){ if(!error){ // file uploaded } });

In my test, I have to setBlobProperties after created the blob in Azure Storage, I will successful set the Content-Disposition of the blob.

E.G.

blobsrv.createBlockBlobFromLocalFile('mycontainer', 'Azure.pdf', 'upload/Azure.pdf', function (error, result, response) {

        if (error) res.send(500);
        blobsrv.setBlobProperties('mycontainer', 'Azure.pdf', { contentDisposition: 'attachment' }, function (error, result, response) {
            res.send(200, JSON.stringify(response));
        })
    });

And the Angular frontend will download the file instead of directly opening it in FireFox browser in my test.

<a href="{{pdfurl}}" download="Azure.pdf">Azure.pdf</a>
$scope.pdfurl = "http://<cantainer>.blob.core.windows.net/mycontainer/Azure.pdf";
  • Odd, it is showing the content disposition as attachment, but in firefox it is still opening in the browser instead of downloading. – Kode Jan 05 '16 at 16:50
1

You can add a "Content-disposition: attachment" header to the response that serves the file. Since your file is stored in Azure Blob storage, you're going to have to proxy the request through your node app.

So the original code would be changed to something like:

$scope.fileURL = '<your-node-app-address>/myfile.pdf';

and

<a href="{{fileURL}}" class="btn btn-default" download="myfile.pdf">Download File</a>

Then you would set up a route in your node app at <your-node-app-address>/myfile.pdf and just attach the correct header, with something like:

var client = restify.createStringClient({ 
        url: 'https://myblob.blob.core.windows.net'
    });    
client.get('/8282020/myfile.pdf', function(err, req, res, data) {
            response.setHeader('Content-Disposition', 'attachment');
            response.writeHead(res.statusCode);
            response.write(data);
            response.end();
        });
theadriangreen
  • 2,218
  • 1
  • 14
  • 14
  • Is restify a node module or built in? – Kode Dec 18 '15 at 23:33
  • 1
    Yes, it's a built in node module, but that's just sample code. Essentially you just need to make another request to your blob, and attach the header, which is what I was trying to get across. – theadriangreen Dec 18 '15 at 23:41
  • How would I find my node app address? (Probably something basic I am missing) – Kode Dec 19 '15 at 00:00
  • 1
    If it's hosted on the same domain, then you would be fine with $scope.fileURL = '/myfile.pdf'; you're just repointing the request to your app so you can proxy it and attach the header on the response. – theadriangreen Dec 19 '15 at 00:03
  • It should be, its an Angular App living on top of Node.js – Kode Dec 19 '15 at 00:05
  • One last challenge. My blob containers change/are dynamic. Any ideas on how to pass this in? – Kode Dec 19 '15 at 03:27
1

There are two ways for doing this without streaming the contents through your web server and both of them involve setting Content-Disposition property on the blob itself.

  1. Always Download: If you want the file to always download, what you could do is set the Content-Disposition property on the blob itself. That way whenever someone access the blob, the file will always be downloaded.
  2. Download Sometimes: If you want the file to download sometimes while open up the browser sometimes, what you can do is create a Shared Access Signature (SAS) on the blob with at least Read permissions and override Content-Disposition response header in the SAS when you want to download the file. If you want to open the file in the browser, simply omit this response header in the SAS.

Please read Overriding Commonly Used Headers in SAS in my blog: http://gauravmantri.com/2013/11/28/new-changes-to-windows-azure-storage-a-perfect-thanksgiving-gift/.

UPDATE

but how do I set the content-disposition in Azure Blob Storage? Is that something I can set across all my blob containers using something like Azure Storage Explorer? Or do I need to set it per blob/how would I do this?

This is something you would need to do for every blob in your container. You can't do it at the container level. I don't think Azure Storage Explorer supports this functionality. In the tool I am building (Cloud Portam - http://www.cloudportam.com), I have included this functionality but again you would have to do for each individual blob. Other alternative is to update the properties of the blob using any client SDK. The approach would be to list the blobs in a container (which should give you blob properties), update ContentDisposition property and save the properties.

UPDATE 2

Can I set the disposition on initial upload? I am using Node/JavaScript: blobSvc.createBlockBlobFromLocalFile('mycontainer', 'myblob', 'test.txt', function(error, result, response){ if(!error){ // file uploaded } });

Sure you can! Take a look at the source code for createBlockBlobFromLocalFile here: https://github.com/Azure/azure-storage-node/blob/master/lib/services/blob/blobservice.js. You can set content-diposition property by providing appropriate value in options.contentDisposition.

Gaurav Mantri
  • 128,066
  • 12
  • 206
  • 241
  • #1 may be what I am looking for. I read your blog (great by the way), but how do I set the content-disposition in Azure Blob Storage? Is that something I can set across all my blob containers using something like Azure Storage Explorer? Or do I need to set it per blob/how would I do this? – Kode Dec 19 '15 at 06:37
  • Updated my response with answer to your comments. HTH. – Gaurav Mantri Dec 20 '15 at 04:50
  • Can I set the disposition on initial upload? I am using Node/JavaScript: blobSvc.createBlockBlobFromLocalFile('mycontainer', 'myblob', 'test.txt', function(error, result, response){ if(!error){ // file uploaded } }); – Kode Dec 20 '15 at 18:08
  • 1
    Updated my response with answer to your comment. HTH. – Gaurav Mantri Dec 21 '15 at 03:54
1

try this library http://danml.com/download.html or https://developer.mozilla.org/en/docs/Web/API/File.

they provide a blob interface.

Geo
  • 33
  • 7
0

Setting the content disposition and content type via Node appears to be working. It appears that both are needed.

blobSvc.setBlobProperties(containerName, filename, { contentDisposition: 'attachment', contentType: 'application/octet-stream' }, function (error, result, response) {
// result code here....
})
Kode
  • 3,073
  • 18
  • 74
  • 140