58

Chrome 65 removed support for the download attribute on anchor elements with cross-origin hrefs:

Block cross-origin <a download>

To avoid what is essentially a user-mediated cross-origin information leakage, Blink will now ignore the presence of the download attribute on anchor elements with cross origin attributes. Note that this applies to HTMLAnchorElement.download as well as to the element itself.

Intent to Remove | Chromestatus Tracker | Chromium Bug

This breaks serverless downloads (for cross-origin resources). It has also broken Reddit Enhancement Suite's save image button (.res-media-controls-download) RES v5.12.0 fixed this by using the chrome.downloads API (the extension now requests your permission to Manage downloads)

Any workaround?

More details in the Web spec, thanks @jbmilgrom

Community
  • 1
  • 1
Leeroy
  • 2,003
  • 1
  • 15
  • 21
  • 4
    _"This breaks serverless downloads"_ - only cross-origin ones, and it does not "break" them, it disables them _on purpose_. (Ok, I guess the seat belt "breaks" your flow through the windshield as well, if you so like ... point that this is deliberate still stands, and therefor asking for "workarounds" is rather nonsense IMHO.) – CBroe Mar 25 '18 at 10:32
  • 1
    I mean any way to not break the web. If I loaded that resource from another origin and I'm looking at it, why should I be banned from downloading it? – Leeroy Mar 25 '18 at 10:32
  • 2
    I understand perfectly well, this is why I'm not filing a bug requesting changes, but asking for a workaround. That being said in my humble opinion hot-linking should be addressed by other means, which are ample. – Leeroy Mar 25 '18 at 10:43
  • 1
    And I'm saying, even if there currently was a workaround (not saying there wasn't), that would only mean their implementation of this measure was still lacking, and they would most likely amend it in that regard as soon as possible ... I doubt it's worth it to get into that cat-and-mouse game to begin with. I guess if you went with a browser extension instead of just a user script inserted into the web site context, there should be possibilities to trigger the browser's actual, native "safe as ..." functionality from there. – CBroe Mar 25 '18 at 10:53
  • Yeah, a browser extension would be an idea, thanks! I think it's worse than a userscript because of the overhead (RAM and maintenance). But then again, when it comes to overhead nothing can beat the 8 characters of the `download` attribute. – Leeroy Mar 25 '18 at 10:58
  • 18
    "This is not about your personal little use case ... It's about preventing web sites from "hot-linking" files for download purposes from 3rd-party sources, " And yet the browser still displays those images, downloading them to working memory. What exactly is the difference between that and saving the image? What about right click -> save link as? What about opening the developer console and saving the image from the sources tab in developer tools? Your little condescending replies help no one, why bother replying? – Amoliski May 22 '18 at 03:12
  • 2
    "your personal little extremely common and logical use case" – stackers Nov 01 '21 at 19:56

2 Answers2

85

According to the discussion blob: and data: URLs are unaffected, so here is a workaround using fetch and Blobs.

Client-side force download media

function forceDownload(blob, filename) {
  var a = document.createElement('a');
  a.download = filename;
  a.href = blob;
  // For Firefox https://stackoverflow.com/a/32226068
  document.body.appendChild(a);
  a.click();
  a.remove();
}

// Current blob size limit is around 500MB for browsers
function downloadResource(url, filename) {
  if (!filename) filename = url.split('\\').pop().split('/').pop();
  fetch(url, {
      headers: new Headers({
        'Origin': location.origin
      }),
      mode: 'cors'
    })
    .then(response => response.blob())
    .then(blob => {
      let blobUrl = window.URL.createObjectURL(blob);
      forceDownload(blobUrl, filename);
    })
    .catch(e => console.error(e));
}

downloadResource('https://giant.gfycat.com/RemoteBlandBlackrussianterrier.webm');

However, fetch only works on some URLs. You may get a CORS error:

Failed to load https://i.redd.it/l53mxu6n14o01.jpg: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://redditp.com' is therefore not allowed access.

There are extensions that let you intercept and modify or delete websites' security headers:

ModHeader - Chrome Web Store

(But setting Access-Control-Allow-Origin: * broke YouTube for me)

Performance

Please note that this approach isn't very performant! At times I've had downloads stall for <1min. The rest of the page was responsive during this time though. I haven't looked into this, but I imagine creating large Blobs is resource intensive.

Violentmonkey / Tampermonkey

If your use case is userscripts, there's GM_download(options), GM_download(url, name)

⚠ In Tampermonkey this is a beta feature, and you must first set Download Mode: [Browser API ▾] in Tampermonkey Dashboard > Settings

Tampermonkey Dashboard > Settings > Downloads

Leeroy
  • 2,003
  • 1
  • 15
  • 21
  • 2
    Wondering what if file size is over 500MB? What are my options? Do I need to use StreamSaver.js? What are my other options? Thanks! – Rich Episcopo Sep 10 '18 at 23:43
  • 1
    I know the question was about chrome, but after getting this to work nicely in chrome it doesn't work in Firefox or the MS browsers. – nfdavenport Feb 05 '19 at 03:37
  • 1
    @nfdavenport Sorry I forgot, in Firefox you have to add the element to the DOM to be able to `a.click()` https://stackoverflow.com/a/32226068 Seems to work, maybe that was all there was to it. – Leeroy Feb 05 '19 at 12:09
  • @Leeroy Thank you that worked for Firefox and Edge. I didn't feel like polyfilling my app for IE11 to get the fetch to work, but if I had IE11 may have worked too. I got a "ReferenceError: 'fetch' is undefined", but since this is a new project I'll try making a stand on IE11 support. :) – nfdavenport Feb 06 '19 at 05:22
  • In my experience, the entire file was downloaded before a user was prompted to save the location. Is there a way to make this function more like a regular download, where a user can watch the progress in real time and choose the save location beforehand? – Anthony Apr 07 '21 at 01:31
  • Trying this with `mode: 'no-cors'` I get a 403 on the target I can access when just pasting the link. Do I have to send some login cookies? – Jaleks Jun 01 '21 at 11:23
  • 1
    It's not a way to go for large files, e.g. 1GB+ videos – rudyryk Jan 12 '23 at 08:23
20

Apparently, the web specification changed at some point to disallow cross-origin downloads. Add content-disposition: attachment header in the response and cross-origin downloads may work again.

jbmilgrom
  • 20,608
  • 5
  • 24
  • 22
  • 2
    example for nginx: `add_header Content-Disposition "attachment;"; ` – mamian Nov 26 '19 at 11:56
  • This indeed worked for me, but created a new problem: iframes whose `src` url has the above response headers are now behaving unexpectedly: they are causing an auto-download of the src file as soon as they are rendered, and additionally their content is blank. – Liran H Jul 08 '20 at 16:11
  • @LiranH did you ever figure out a solution? – Anthony Apr 06 '21 at 17:22
  • @Anthony Afraid not – Liran H Apr 11 '21 at 13:28
  • @LiranH I'm looking into conditionally setting the header if the request comes from a certain domain versus another. It seems tricky with nginx maybe there is another way to do so. – Anthony Apr 12 '21 at 08:43