Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Making Firefox and Chrome download image under a specific name

Tags:

Given https://www.example.com/image-list:

...
<a href="/image/1337">
  <img src="//static.example.com/thumbnails/86fb269d190d2c85f6e0468ceca42a20.png"/>
</a>
<a href="//static.example.com/full/86fb269d190d2c85f6e0468ceca42a20.png"
   download="1337 - Hello world!.png">
  Download
</a>
...

This is a user script environment, so I have no access to server configuration. As such:

  1. I can't make server accept user-friendly file names like https://static.example.com/full/86fb269d190d2c85f6e0468ceca42a20 - 1337 - Hello World!.png.
  2. I can't configure Cross-Origin Resource Sharing. www.example.com and static.example.com are separated by CORS wall by design.

How to make Firefox and Chrome display Save File As dialog with the suggested file name "1337 - Hello world!.png" when a user clicks on the "Download" link?

After some failing and googling, I learned these problems:

  1. Firefox completely ignores existence of the download attribute on some image MIME types.
  2. Firefox completely ignores existence of the download attribute on cross-site links.
  3. Chrome completely ignores value of the download attribute on cross-site links.

All these points don't make any sense to me, all look like "let's put random non-sensical limitations on the feature", but I have to accept them as it's my environment.

Do any ways to solve the problem exist?


Background: I'm writing a user script for an image board which uses MD5 hashes as file names. I want to make saving with user-friendly names easier. Anything which gets me closer to this would be helpful.

I guess I can get around the limitations by using object URLs to blobs and a local proxy with hacked CORS headers, but this setup is obviously beyond reasonable. Saving through canvas could work (are images "protected" by CORS in this case too?), but it will either force double lossy compression or lossy-to-lossless conversion, given JPEG files, neither of which are good.

like image 321
Athari Avatar asked Jan 07 '18 05:01

Athari


2 Answers

All modern browsers will ignore the download attribute in the anchor tag for cross-origin URL'S.

Reference : https://html.spec.whatwg.org/multipage/links.html#downloading-resources

According to the spec makers, this represents a security loophole as a user could be tricked into downloading malicious files while browsing a secure site, believing that the file is also originating from the same secure site.

Any interesting conversation for implementing this feature in the firefox browser can be found here : https://bugzilla.mozilla.org/show_bug.cgi?id=676619


[ Edit by Athari ]

Quote from specification:

This could be dangerous, because, for instance, a hostile server could be trying to get a user to unknowingly download private information and then re-upload it to the hostile server, by tricking the user into thinking the data is from the hostile server.

Thus, it is in the user's interests that the user be somehow notified that the resource in question comes from quite a different source, and to prevent confusion, any suggested filename from the potentially hostile interface origin should be ignored.

Clarification on the mysterious scenario:

the more serious issue with CORS downloads is if a malicious site forces a download of a file form a legitimate site and some how gets access to its content. so lets say I download the user gmail inbox page and explore its messages.

in this case an evil site will have to fool the user into downloading the file and uploading it back to the server, so lets say we have a gmail.com/inbox.html actually contains all the user mail messages and the attacker sites offers a download link for a coupon file, that should be uploaded to another evil site. the coupon will supposedly offer a 30% discount on a new Ipad. the download link will actually point to gmail.com/inbox.html and will download it as "30off.coupon", the if the user will download this file and upload it without checking it's content the evil site will get the user "coupon" and so its inbox content.

Important notes:

  1. Google originally didn't limit download attribute by CORS and was explicitly against this. It was later forced to adjust Chrome implementation.

    Google was opposed to using CORS for this.

  2. Alternative solutions were proposed with giving a user a warning about cross-origin downloads. They were ignored.

    Well there can be notification or deny/allow mechanism when downloading from another origin (e.g. like in case of geolocation API). Or not to send cookies in case of cross origin request with download attribute.

  3. Some developers do share the opinion that the restriction is too strong, severely limits the usage of the feature and that the scenario is so complicated that the user who would do this would easily download and run an executable file. Their opinion was disregarded.

    The case against allowing cross-origin downloads is centered around the premise that visitors of an [evil] site (eg, discountipads.com) could unknowingly download a file from a site containing their own personal information (eg, gmail.com) and save it to their disk using a misleading name (eg, "discount.coupon") AND THEN proceed to another malicious page where they manually upload that same file they just downloaded. This is quite far-fetched in my opinion, and anyone who would succumb to such trivial trickery perhaps does not belong online in the first place. I mean c'mon...Click here to download our special discount offer and then re-upload it through our special form! Seriously? Download our special offer and then email it to this Yahoo address for a big discount! Do the people who fall for these things even know how to do email attachments?

    I'm all for browser security, but if the good people of Chromium have no problem with this I don't see why Firefox has to completely banish it. At the very least I'd like to see a preference in about:config to enable cross-origin @download for "advanced" users (default it to false). Even better would be a confirmation box similar to: "Although this page is encrypted, the information you submit through this form won't be" or: "This page is requesting to install addons" or: "Files downloaded from the web may harm your computer" or even: "The security certificate of this page is invalid" ...y'know what I mean? There are myriad ways to heighten the user's awareness and inform them this might not be safe. One extra click and a short (or long?) delay is enough to let them assess the risk.

    As the web grows, and the use of CDNs grows, and the presence of advanced web-apps grows, and the need to manage files hosted across servers grows, features like @download will become more important. And when a browser like Chrome supports it fully whereas Firefox does not, this is not a win for Firefox.

    In short, I think that mitigating the potential evil uses of @download by simply ignoring the attribute in cross-origin scenarios is a woefully ill-thought move. I'm not saying the risk is entirely non-existent, quite the contrary: I am saying there are plenty of risky things one does online in the course of his day...downloading ANY file is high among them. Why not work around that issue with a well-thought user experience?

Overall, considering widespread use of CDNs and intentionally putting user-generated content on a different domain, the primary use for the download attribute is specifying a file name for blob downloads (URL.createObjectURL) and the like. It can't be used in a lot of configurations and certainly not very useful in user scripts.

like image 188
23nigam Avatar answered Nov 02 '22 11:11

23nigam


Try something like:

  1. Get the external image to your server first
  2. Return the fetched image from your server.
  3. Dynamically create an anchor with download name and .click() it!

while the above was just a pretty short tips list... give this a try:

on www.example.com place a fetch-image.php with this content:

<?php
$url = $_GET["url"];                     // the image URL
$info = getimagesize($url);              // get image data
header("Content-type: ". $info['mime']); // act as image with right MIME type
readfile($url);                          // read binary image data
die();

or with any other server-side language that achieves the same.

The above should return any external image as it's sitting on your domain.

On your image-list page, what you can try now is:

<a 
  href="//static.example.com/thumbnails/86fb269d190d2c85f6e0468ceca42a20.png" 
  download="1337 - Hello world!.png">DOWNLOAD</a>

and this JS:

function fetchImageAndDownload (e) {
    e.preventDefault(); // Prevent browser's default download stuff...

    const url = this.getAttribute("href");       // Anchor href 
    const downloadName = this.download;          // Anchor download name

    const img = document.createElement("img");   // Create in-memory image
    img.addEventListener("load", () => {
        const a = document.createElement("a");   // Create in-memory anchor
        a.href = img.src;                        // href toward your server-image
        a.download = downloadName;               // :)
        a.click();                               // Trigger click (download)
    });
    img.src = 'fetch-image.php?url='+ url;       // Request image from your server

}

[...document.querySelectorAll("[download]")].forEach( el => 
    el.addEventListener("click", fetchImageAndDownload)
);

You should see finally the image downloaded as

1337 - Hello world!.png

instead of 86fb269d190d2c85f6e0468ceca42a20.png like it was the case.

Notice: I'm not sure about the implications of simultaneous requests toward fetch-image.php - make sure to test, test.

like image 31
Roko C. Buljan Avatar answered Nov 02 '22 12:11

Roko C. Buljan